Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Linux 04.06.2026 · 12 min Lesezeit

Caddy als Reverse-Proxy mit automatischem HTTPS einrichten und mit nginx und Traefik vergleichen

Caddy 2.11 holt und erneuert TLS-Zertifikate selbst. Die Anleitung zeigt Installation per APT, Caddyfile für mehrere Anwendungen, Wildcard-Zertifikate per DNS-Challenge, Security-Header und den Umstieg von nginx.

Geprüft am 14.10.2026 · für Caddy 2.11.7

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Serverschrank mit Messingvorhängeschloss auf einem Switch, blaue Netzwerkkabel führen zu drei kleinen Geräten darunter

Ein klassischer Reverse-Proxy auf einem Linux-Server bedeutet: nginx konfigurieren, certbot einrichten, Erneuerung per Timer überwachen und Weiterleitungen von HTTP auf HTTPS von Hand pflegen. Caddy v2 erledigt den Zertifikatsteil selbst. Der in Go geschriebene Webserver holt Zertifikate per ACME (das Protokoll hinter Let's Encrypt), erneuert sie rechtzeitig und leitet HTTP automatisch auf HTTPS um. Diese Anleitung richtet Caddy 2.11.7 als systemd-Dienst auf Debian oder Ubuntu ein, zeigt ein Caddyfile für mehrere interne Anwendungen samt Wildcard-Zertifikat und erklärt, wann nginx oder Traefik die bessere Wahl sind.

Voraussetzungen

  • Linux-Server mit Debian 12 oder 13 bzw. Ubuntu 22.04 oder 24.04, x86_64 oder ARM64. Caddy selbst kommt mit 1 CPU-Kern und 512 MB RAM aus (im Test rund 70 MB im Leerlauf), dazu kommen die Anwendungen dahinter. Für einen typischen Server mit mehreren Diensten sind 2 Kerne und 4 GB RAM sinnvoll.
  • Root- oder sudo-Zugriff.
  • Für öffentliche Zertifikate per HTTP-Challenge: eine Domain, deren A- bzw. AAAA-Eintrag auf den Server zeigt, und die Ports 80 und 443 aus dem Internet erreichbar.
  • Für Wildcard-Zertifikate: DNS-Zone bei einem Anbieter mit API (im Beispiel Cloudflare) und ein API-Token mit Schreibrecht auf die Zone. Für den eigenen Build Go ab Version 1.26 oder Docker.
  • Korrekte Systemzeit (systemd-timesyncd oder chrony aktiv), sonst schlagen ACME-Prüfungen fehl.
  • Ports 80 und 443 frei. Läuft dort nginx oder Apache, migrieren Sie wie in Schritt 7 beschrieben.

Schritt 1: Caddy, nginx oder Traefik auswählen

Alle drei arbeiten zuverlässig als Reverse-Proxy. Sie unterscheiden sich vor allem darin, wie viel Konfiguration und Pflege anfällt.

KriteriumnginxCaddy v2Traefik v3
Zertifikate automatischnein, certbot nötigja, eingebautja, eingebaut
HTTP auf HTTPS umleiteneigene Regelautomatisch (308)per Konfiguration
Wildcard-Zertifikatecertbot mit DNS-Plugineigener Build mit DNS-Plugineingebaute DNS-Anbieter
Konfiguration aus Docker-Labelsneinnur mit Pluginja
Konfigurationsaufwandhochgeringmittel
Typischer EinsatzHochlast, bestehende SetupsServer mit festem Satz an AnwendungenDocker- und Kubernetes-Stacks

Faustregel: Caddy für einen Server mit einer überschaubaren, stabilen Zahl von Anwendungen, wenn wenig Pflegeaufwand zählt. nginx, wenn Sie bereits umfangreiche Konfigurationen haben oder sehr hohe Last erwarten. Traefik, wenn Container laufend dazukommen und sich selbst anmelden sollen. Details finden Sie in den Anleitungen zu Traefik in Docker-Stacks, zu nginx mit TLS und für Caddy im Container in der Caddy-Anleitung mit Docker Compose.

Verifizieren: Sie können in einem Satz begründen, warum Caddy zu Ihrem Server passt, und haben eine Liste der Anwendungen samt internem Port, die später hinter dem Proxy laufen.

Schritt 2: Caddy aus dem offiziellen Repository installieren

Das Caddy-Projekt stellt ein APT-Repository bei Cloudsmith bereit. Die Befehle entsprechen der offiziellen Installationsanleitung:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Das Paket legt den Benutzer caddy an, installiert die Unit caddy.service und startet den Dienst. Die Unit läuft als Benutzer caddy und bringt die Capability CAP_NET_BIND_SERVICE über AmbientCapabilities mit, deshalb darf Caddy die Ports 80 und 443 belegen. Die Konfiguration liegt in /etc/caddy/Caddyfile, Zertifikate und Schlüssel unter /var/lib/caddy/.local/share/caddy/.

Verifizieren: caddy version zeigt v2.11.7 oder neuer, systemctl is-active caddy meldet active und curl -I http://localhost liefert Status 200 mit der Caddy-Willkommensseite.

Schritt 3: Erste Anwendung mit automatischem HTTPS veröffentlichen

Ersetzen Sie den Inhalt von /etc/caddy/Caddyfile durch einen Site-Block. Die Domain davor genügt, damit Caddy ein Zertifikat holt und Port 80 auf HTTPS umleitet:

app.example.com {
	reverse_proxy localhost:8080
}

Ersetzen Sie app.example.com durch Ihre Domain und 8080 durch den Port der Anwendung. Caddy setzt die Header X-Forwarded-For, X-Forwarded-Proto und X-Forwarded-Host automatisch, sodass die Anwendung Client-IP und Protokoll kennt. Laden Sie die Konfiguration mit sudo systemctl reload caddy. Beim ersten Aufruf holt Caddy das Zertifikat, das dauert einige Sekunden.

Für Tests ohne öffentliche Domain gibt es die globale Option local_certs. Caddy stellt dann alle Zertifikate über seine eigene interne Zertifizierungsstelle aus. Die Wurzel liegt in /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt; diese Datei müssen Sie auf den Clients als vertrauenswürdig hinterlegen.

{
	local_certs
}

app.example.test {
	reverse_proxy localhost:8080
}

Verifizieren: curl -sI http://app.example.com antwortet mit 308 Permanent Redirect und Location: https://app.example.com/. curl -sI https://app.example.com liefert den Status der Anwendung, im Header steht via: 1.1 Caddy. Bei local_certs prüfen Sie mit curl --cacert root.crt; der Aussteller heißt dann Caddy Local Authority - ECC Intermediate.

Schritt 4: Eigenen Build mit DNS-Plugin für Wildcard-Zertifikate

Wildcard-Zertifikate wie *.intern.example.com gibt es nur über die DNS-Challenge. Caddy legt dafür einen TXT-Eintrag bei Ihrem DNS-Anbieter an. Das APT-Paket enthält keine DNS-Plugins, Sie bauen Caddy deshalb mit xcaddy. Am einfachsten geht das ohne lokale Go-Installation mit dem offiziellen Builder-Image:

cd /tmp
docker run --rm -v "$PWD":/out caddy:2.11.7-builder \
  sh -c 'xcaddy build v2.11.7 --with github.com/caddy-dns/cloudflare --output /out/caddy'
./caddy list-modules | grep cloudflare

Mit installiertem Go ab 1.26 geht es auch direkt: go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest und danach xcaddy build v2.11.7 --with github.com/caddy-dns/cloudflare. Der Build dauert auf einem kleinen Server einige Minuten.

Damit ein späteres apt upgrade die eigene Binärdatei nicht überschreibt, empfiehlt die Caddy-Dokumentation dpkg-divert und update-alternatives. Unit, Benutzer und Konfiguration aus dem Paket bleiben so erhalten:

sudo dpkg-divert --divert /usr/bin/caddy.default --rename /usr/bin/caddy
sudo mv ./caddy /usr/bin/caddy.custom
sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.default 10
sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.custom 50
sudo systemctl restart caddy

Ein setcap ist dafür nicht nötig, weil die systemd-Unit die Capability für Port 80 und 443 mitbringt. Nur wenn Sie Caddy außerhalb von systemd als normaler Benutzer starten, brauchen Sie sudo setcap cap_net_bind_service=+ep /usr/bin/caddy.custom.

Verifizieren: caddy list-modules | grep dns.providers zeigt dns.providers.cloudflare, update-alternatives --display caddy nennt /usr/bin/caddy.custom als aktuelle Auswahl und systemctl is-active caddy meldet active.

Schritt 5: API-Token sicher hinterlegen

Das Token gehört nicht in das Caddyfile, sondern in eine eigene Datei, die nur root und der Benutzer caddy lesen dürfen. Legen Sie in Cloudflare ein Token mit dem Recht „Zone DNS bearbeiten“ nur für die betroffene Zone an.

sudo mkdir -p /etc/caddy/secrets
echo 'CLOUDFLARE_API_TOKEN=IHR_TOKEN' | sudo tee /etc/caddy/secrets/cloudflare.env >/dev/null
sudo chown root:caddy /etc/caddy/secrets/cloudflare.env
sudo chmod 640 /etc/caddy/secrets/cloudflare.env
sudo systemctl edit caddy

Im Editor tragen Sie den Override ein und speichern:

[Service]
EnvironmentFile=/etc/caddy/secrets/cloudflare.env

systemctl edit lädt die Units danach selbst neu. Ein leeres oder falsches Token erkennt Caddy schon beim Laden der Konfiguration und meldet API token '' appears invalid.

Verifizieren: sudo ls -l /etc/caddy/secrets/cloudflare.env zeigt -rw-r----- root caddy, systemctl show caddy -p EnvironmentFiles nennt den Pfad der Datei.

Schritt 6: Caddyfile für mehrere interne Anwendungen

Das folgende Caddyfile veröffentlicht drei Anwendungen unter einem gemeinsamen Wildcard-Zertifikat. Unbekannte Subdomains erhalten eine 404-Antwort, statt versehentlich bei einer Anwendung zu landen. Der globale Block schreibt das Betriebsprotokoll von Caddy in eine Datei und vertraut vorgelagerten Proxys aus privaten Netzen.

{
	email admin@example.com
	log default {
		output file /var/log/caddy/caddy.log
		format json
	}
	servers {
		trusted_proxies static private_ranges
	}
}

*.intern.example.com {
	tls {
		dns cloudflare {env.CLOUDFLARE_API_TOKEN}
	}

	@gitea host gitea.intern.example.com
	handle @gitea {
		reverse_proxy localhost:3000
	}

	@cloud host cloud.intern.example.com
	handle @cloud {
		reverse_proxy localhost:8080 {
			health_uri /status.php
			health_interval 30s
		}
	}

	@grafana host grafana.intern.example.com
	handle @grafana {
		reverse_proxy localhost:3001
	}

	handle {
		respond "Unbekannter Dienst" 404
	}
}

Das Protokollverzeichnis muss dem Benutzer caddy gehören: sudo mkdir -p /var/log/caddy && sudo chown caddy:caddy /var/log/caddy. Caddy legt Protokolldateien standardmäßig mit Modus 600 an. Die DNS-Challenge funktioniert auch, wenn der Server selbst nicht aus dem Internet erreichbar ist; die DNS-Einträge für die Subdomains zeigen dann auf die interne Adresse.

Verifizieren: caddy validate --config /etc/caddy/Caddyfile endet mit Valid configuration. Nach sudo systemctl reload caddy zeigt journalctl -u caddy die Meldung certificate obtained successfully für *.intern.example.com. curl -s https://gitea.intern.example.com liefert die Anwendung, curl -s https://test.intern.example.com den Text Unbekannter Dienst mit Status 404.

Schritt 7: Security-Header, Kompression und Zugriffsprotokoll

Caddy setzt keine Security-Header wie HSTS von sich aus. Ein vollständiger Site-Block sieht so aus:

app.example.com {
	encode gzip zstd
	header {
		Strict-Transport-Security "max-age=31536000; includeSubDomains"
		X-Content-Type-Options nosniff
		X-Frame-Options DENY
		Referrer-Policy strict-origin-when-cross-origin
		-Server
	}
	reverse_proxy localhost:9000 {
		health_uri /health
		health_interval 15s
		health_timeout 5s
	}
	log {
		output file /var/log/caddy/app-access.log
		format json
	}
}

Das Zugriffsprotokoll entsteht nur durch die Direktive log im Site-Block. Der globale Block log default aus Schritt 6 betrifft das Betriebsprotokoll. Den Zusatz preload bei HSTS setzen Sie erst, wenn alle Subdomains dauerhaft HTTPS sprechen, weil die Aufnahme in die Preload-Listen der Browser nur langsam rückgängig zu machen ist. health_uri nur eintragen, wenn die Anwendung diesen Pfad wirklich bereitstellt; sonst markiert Caddy das Ziel als nicht erreichbar.

Verifizieren: curl -sI https://app.example.com enthält strict-transport-security, x-content-type-options: nosniff und x-frame-options: DENY, aber keinen server-Header. curl -s -o /dev/null -D - -H 'Accept-Encoding: zstd' https://app.example.com/ zeigt bei komprimierbaren Antworten ab einigen hundert Byte content-encoding: zstd.

Schritt 8: Von nginx zu Caddy wechseln

Eine nginx-Konfiguration mit HTTP-Umleitung, Zertifikatspfaden und Proxy-Headern schrumpft in Caddy auf wenige Zeilen, weil Umleitung, Zertifikat und Forwarded-Header entfallen:

# vorher: nginx
server {
    listen 80;
    server_name app.example.com;
    return 301 https://$host$request_uri;
}
server {
    listen 443 ssl;
    http2 on;
    server_name app.example.com;
    ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
# nachher: Caddy
app.example.com {
	reverse_proxy localhost:8080
}

So wechseln Sie mit kurzer Unterbrechung:

  1. Caddyfile vollständig schreiben und mit caddy validate --config /etc/caddy/Caddyfile prüfen.
  2. nginx stoppen und deaktivieren: sudo systemctl disable --now nginx.
  3. Caddy neu starten: sudo systemctl restart caddy. Beim ersten Start holt Caddy die Zertifikate, die Seite ist nach wenigen Sekunden erreichbar.
  4. Bei Problemen zurück: sudo systemctl stop caddy && sudo systemctl enable --now nginx. Die alten certbot-Zertifikate bleiben bis dahin unangetastet.

Verifizieren: sudo ss -tlnp | grep -E ':(80|443) ' zeigt nur noch caddy, systemctl is-enabled nginx meldet disabled, und die Seite öffnet im Browser ohne Zertifikatswarnung.

Schritt 9: Änderungen prüfen und ohne Unterbrechung neu laden

Caddy lädt eine neue Konfiguration im laufenden Betrieb. Ist sie fehlerhaft, verwirft Caddy sie und arbeitet mit der alten weiter. Im Test führte ein Tippfehler in einer Unterdirektive zur Meldung unrecognized subdirective health_urii, die Anwendung blieb mit Status 200 erreichbar.

caddy fmt --overwrite /etc/caddy/Caddyfile
caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo journalctl -u caddy --since '10 min ago' | grep -i error
echo | openssl s_client -connect app.example.com:443 -servername app.example.com 2>/dev/null | openssl x509 -noout -issuer -enddate

caddy fmt vereinheitlicht Einrückungen. caddy validate braucht für Dateien, die nicht Caddyfile heißen, zusätzlich --adapter caddyfile, sonst erwartet Caddy JSON.

Verifizieren: systemctl status caddy zeigt active (running), das Journal enthält keinen Fehler zum letzten Reload, und openssl nennt als Aussteller Let's Encrypt oder ZeroSSL mit einem notAfter-Datum in der Zukunft.

Typische Fehler

  • listen tcp :443: bind: address already in use: nginx oder Apache belegt den Port. sudo ss -tlnp | grep ':443 ' zeigt den Prozess, dann wie in Schritt 8 umstellen.
  • unknown module: dns.providers.cloudflare: Es läuft die Paketversion ohne Plugin. Eigenen Build aus Schritt 4 einrichten und mit update-alternatives --display caddy prüfen.
  • API token '' appears invalid: Die Umgebungsvariable kommt nicht an. Override aus Schritt 5 prüfen, Dateirechte 640 root:caddy.
  • config is not valid JSON bei caddy validate: Datei heißt nicht Caddyfile. --adapter caddyfile ergänzen.
  • Falsche Client-IPs in der Anwendung: Vor Caddy sitzt ein weiterer Proxy oder Load Balancer. trusted_proxies im globalen Block wie in Schritt 6 setzen.
  • Rate-Limit bei Let's Encrypt: Höchstens 50 neue Zertifikate je registrierter Domain und 5 identische Zertifikate in 7 Tagen. Für Versuche im globalen Block acme_ca https://acme-staging-v02.api.letsencrypt.org/directory setzen oder mit local_certs arbeiten.
  • ACME-Prüfung schlägt fehl: DNS-Eintrag zeigt nicht auf den Server, Port 80 bzw. 443 ist von außen gesperrt oder die Systemzeit weicht ab. timedatectl status und die Firewall prüfen, Hinweise dazu in Linux-Server mit UFW und Fail2ban absichern.
  • Kein Zugriffsprotokoll: Die Direktive log fehlt im Site-Block oder das Verzeichnis gehört nicht dem Benutzer caddy.

Häufige Fragen

Brauche ich certbot noch, wenn ich Caddy nutze?

Nein. Caddy holt und erneuert Zertifikate selbst, rechtzeitig vor Ablauf und ohne Timer. Wer bei nginx bleibt, findet die klassische Variante in der Anleitung Let's Encrypt mit certbot.

Wann ist Traefik die bessere Wahl?

Wenn Container häufig wechseln und sich über Labels selbst beim Proxy anmelden sollen. Caddy passt besser, wenn die Zahl der Anwendungen überschaubar ist und Sie die Konfiguration lieber in einer lesbaren Datei pflegen.

Funktioniert ein Wildcard-Zertifikat für interne Dienste ohne offene Ports?

Ja. Bei der DNS-Challenge prüft die Zertifizierungsstelle nur den TXT-Eintrag in Ihrer DNS-Zone. Der Server muss dafür nicht aus dem Internet erreichbar sein, nur die API des DNS-Anbieters erreichen.

Was passiert, wenn Let's Encrypt nicht erreichbar ist?

Caddy versucht es laut Dokumentation automatisch bei ZeroSSL. Scheitern beide, wiederholt Caddy die Versuche mit wachsendem Abstand bis zu 30 Tage lang. Noch gültige Zertifikate bleiben in dieser Zeit in Betrieb.

Setzt Caddy Security-Header automatisch?

Nein. Caddy ergänzt nur die Forwarded-Header für die Anwendung dahinter. HSTS, X-Frame-Options und ähnliche Header konfigurieren Sie wie in Schritt 7 über den Block header.

Fazit

Mit Caddy reduziert sich ein Reverse-Proxy mit HTTPS, automatischer Erneuerung und Umleitung auf wenige Zeilen Konfiguration. Aufwand entsteht nur bei Wildcard-Zertifikaten durch den eigenen Build und bei den Security-Headern, die Sie selbst setzen. Für Server mit festem Satz an Anwendungen in kleinen Unternehmen und im Homelab ist Caddy damit eine pflegeleichte Wahl. Für dynamische Container-Umgebungen bleibt Traefik naheliegender, für große bestehende Konfigurationen nginx.

Weiterführende Anleitungen und Quellen