Caddy als Reverse-Proxy mit automatischem HTTPS – wann er nginx und Traefik schlägt
Caddy v2 übernimmt TLS-Zertifikate, Renewal und OCSP-Stapling vollautomatisch – ohne certbot, ohne Cron-Job. Wann Caddy besser ist als nginx und Traefik, plus produktionsreifes Setup mit Wildcard-Zertifikaten und nginx-Migration.

Wer einen Reverse-Proxy auf einem Bare-Metal-Server betreibt, kennt das Ritual: nginx konfigurieren, certbot installieren, Cron-Job für die Zertifikatserneuerung einrichten, OCSP-Stapling manuell aktivieren, HTTP-zu-HTTPS-Redirects schreiben. Caddy v2 streicht all das. Der in Go geschriebene Webserver holt sich TLS-Zertifikate von Let's Encrypt und ZeroSSL vollautomatisch, erneuert sie mit exponentiellem Backoff und erledigt OCSP-Stapling ohne einen einzigen zusätzlichen Handgriff. Diese Anleitung zeigt dir, wann Caddy die richtige Wahl ist, wann du besser bei nginx oder Traefik bleibst, und wie du ein produktionsreifes Setup mit mehreren internen Apps inklusive Wildcard-Zertifikat in Betrieb nimmst.
Voraussetzungen
- Linux-Server (Bare Metal oder VM) mit Debian/Ubuntu 22.04+ oder RHEL/CentOS 8+
- Root- oder sudo-Zugriff auf den Server
- Öffentlich erreichbare Domain (für HTTP-01-Challenge: Port 80 und 443 zugänglich; für DNS-01/Wildcard: nur DNS-API-Zugang nötig)
- DNS-Zone bei einem unterstützten Provider (z. B. Cloudflare) mit API-Schreib-Token – nur für Wildcard-Zertifikate
- Go-Toolchain (go 1.21+) – nur für Custom-Build mit xcaddy (Wildcard-DNS-Plugin)
- NTP-Synchronisation aktiv (systemd-timesyncd oder chrony) – Pflicht für ACME-Validierung
- Keine laufenden Dienste auf Port 80 und 443 (nginx/Apache vorher stoppen)
- ACME-E-Mail-Adresse für Zertifikats-Benachrichtigungen
Schritt 1: Entscheidungsmatrix – Caddy, nginx oder Traefik?
Bevor du Zeit in die Migration investierst, solltest du ehrlich prüfen, ob Caddy für deinen Anwendungsfall passt. Die folgende Tabelle fasst die wesentlichen Unterschiede zusammen – basierend auf Benchmarks mit einem Intel i5-12400 unter Ubuntu 24.04:
| Kriterium | nginx | Caddy v2 | Traefik v3 |
|---|---|---|---|
| Durchsatz (statische Inhalte) | 45.230 req/s | 38.450 req/s (~15 % weniger) | 31.890 req/s |
| RAM idle | ~85 MB | ~120 MB | ~180 MB |
| RAM bei 50.000 WebSockets | 1,2 GB | 1,4 GB | 1,8 GB |
| Automatisches TLS (ohne certbot) | Nein | Ja | Ja |
| HTTP→HTTPS-Redirect automatisch | Nein (explizit) | Ja | Ja |
| Wildcard-Zertifikate | Via certbot + Cron | Ja (Custom-Build nötig) | Ja |
| Docker-Service-Discovery | Nein | Nein | Ja (Labels) |
| Bare-Metal / stabiler App-Stack | Gut | Optimal | Möglich |
| Config-Aufwand | Hoch | Minimal | Mittel |
Faustregel: Nimm Caddy, wenn du auf Bare Metal mehrere interne Apps proxyst, minimalen Pflegeaufwand willst und keine Hochlast-Site betreibst. Bleib bei nginx, wenn Performance jede Millisekunde zählt oder du bestehende, komplexe Konfigurationen hast. Wähle Traefik, wenn dein Stack auf Docker oder Kubernetes basiert. Unsere Traefik-Anleitung für Docker-Stacks sowie die nginx-Reverse-Proxy-Anleitung decken diese Szenarien im Detail ab.
Schritt 2: Caddy installieren (Standard-APT-Paket)
Für öffentliche Domains ohne Wildcard-Bedarf reicht das offizielle APT-Paket vollständig aus:
# Installation via APT (Debian/Ubuntu)
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 apt update && sudo apt install caddy
Nach der Installation läuft Caddy als systemd-Service unter dem User caddy. Zertifikate werden unter /var/lib/caddy/.local/share/caddy/ gespeichert. Die Konfigurationsdatei liegt unter /etc/caddy/Caddyfile.
Schritt 3: Custom-Build mit Cloudflare DNS-Plugin (für Wildcard-Zertifikate)
Das Standard-APT-Paket enthält keine DNS-Provider-Plugins. Für Wildcard-Zertifikate (z. B. *.intern.example.com) via DNS-01-Challenge brauchst du einen Custom-Build mit xcaddy. Die Go-Toolchain muss installiert sein:
# xcaddy installieren
GOBIN=/usr/local/bin go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
# Custom-Build mit Cloudflare-Plugin
cd /tmp
xcaddy build --with github.com/caddy-dns/cloudflare@latest
# Binary ersetzen
sudo mv caddy /usr/bin/caddy
# Verifikation: Cloudflare-Modul muss erscheinen
caddy list-modules | grep cloudflare
Wichtig für Ports unter 1024: Bei einer selbst kompilierten Binary muss die Capability manuell gesetzt werden:
sudo setcap cap_net_bind_service=+ep /usr/bin/caddy
Schritt 4: Cloudflare API-Token sicher hinterlegen
API-Tokens gehören nicht in die Caddyfile selbst, sondern in eine separate Datei mit eingeschränkten Rechten:
# Secrets-Verzeichnis anlegen und Token speichern
sudo mkdir -p /etc/caddy/secrets
echo 'CLOUDFLARE_API_TOKEN=dein_token_hier' | sudo tee /etc/caddy/secrets/cloudflare.env
sudo chown root:caddy /etc/caddy/secrets/cloudflare.env
sudo chmod 640 /etc/caddy/secrets/cloudflare.env
Dann den systemd-Service um einen Override erweitern, der die Umgebungsvariable lädt:
# /etc/systemd/system/caddy.service.d/override.conf
[Service]
EnvironmentFile=/etc/caddy/secrets/cloudflare.env
sudo systemctl daemon-reload
Schritt 5: Produktions-Caddyfile für mehrere interne Apps
Das folgende Caddyfile konfiguriert drei interne Apps (Gitea, Nextcloud, Grafana) unter einem einzigen Wildcard-Zertifikat für *.intern.example.com. Der globale Block aktiviert strukturiertes JSON-Logging und setzt die trusted-proxies-Konfiguration für korrekte Client-IPs:
# /etc/caddy/Caddyfile
{
email admin@example.com
log default {
output file /var/log/caddy/access.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
}
@nextcloud host nextcloud.intern.example.com
handle @nextcloud {
reverse_proxy localhost:8080 {
header_up Host {upstream_hostport}
health_uri /status.php
health_interval 30s
}
}
@grafana host grafana.intern.example.com
handle @grafana {
reverse_proxy localhost:3001
}
handle {
respond 404
}
}
Das Logverzeichnis muss für den caddy-User beschreibbar sein:
sudo mkdir -p /var/log/caddy
sudo chown caddy:caddy /var/log/caddy
Schritt 6: Security-Header und Kompression ergänzen
Caddy setzt Security-Header nicht automatisch – das ist ein häufiges Missverständnis. Sie müssen explizit konfiguriert werden. Das folgende Beispiel zeigt einen vollständigen Site-Block mit HSTS, Kompression und Health-Check:
app.example.com {
encode gzip zstd
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Frame-Options DENY
X-Content-Type-Options nosniff
Referrer-Policy no-referrer-when-downgrade
Permissions-Policy "geolocation=(), camera=()"
-Server
}
reverse_proxy localhost:9000 {
health_uri /health
health_interval 15s
health_timeout 5s
lb_try_duration 3s
}
log {
output file /var/log/caddy/app-access.log
format json
}
}
Schritt 7: Migration von nginx zu Caddy
Der Größenvorteil von Caddy wird beim Vergleich direkt sichtbar. Eine typische nginx-Konfiguration mit SSL, HTTP-Redirect und Proxy-Headern umfasst etwa 22 Zeilen. Das äquivalente Caddyfile braucht sechs:
# Vorher: nginx-Konfiguration (22 Zeilen)
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
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;
add_header Strict-Transport-Security "max-age=31536000";
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Nachher: Caddy-Konfiguration (6 Zeilen)
app.example.com {
reverse_proxy localhost:8080
header Strict-Transport-Security "max-age=31536000"
}
Migrations-Reihenfolge ohne Downtime:
- Caddy auf einem anderen Port testen (z. B. 8080/8443) und Konfiguration validieren
caddy validate --config /etc/caddy/Caddyfileausführen – bei Fehler abbrechen- nginx stoppen und deaktivieren:
sudo systemctl stop nginx && sudo systemctl disable nginx - Caddy starten:
sudo systemctl start caddy– beim ersten Start holt Caddy sofort das Zertifikat (30–120 Sekunden Verzögerung normal) - Status prüfen:
sudo systemctl status caddyundsudo journalctl -u caddy -f
Schritt 8: Konfiguration validieren und Reload
Caddy erlaubt einen graceful Reload ohne Downtime – aktive Verbindungen werden bis zum Ablauf einer konfigurierbaren Grace-Period (Standard: 5 Sekunden) abgewartet. Vor jedem Reload muss die Syntax validiert werden, da ein fehlerhafter Reload lautlos fehlschlägt und die alte Konfiguration weiterläuft:
# Syntax validieren (immer zuerst!)
caddy validate --config /etc/caddy/Caddyfile
# Graceful Reload
sudo systemctl reload caddy
# Alternativ direkt via Caddy CLI
caddy reload --config /etc/caddy/Caddyfile
# Status und Logs prüfen
sudo systemctl status caddy
sudo journalctl -u caddy -f
sudo journalctl -u caddy --since '10 min ago' | grep -i error
# Zertifikatsstatus per openssl prüfen
openssl s_client -servername example.com -connect example.com:443 < /dev/null 2>&1 \
| grep -E 'subject|issuer|notAfter'
Troubleshooting / Typische Fehler
- „Error: listen tcp :443: bind: address already in use" – nginx oder Apache läuft noch auf Port 443. Lösung:
sudo systemctl stop nginx && sudo systemctl disable nginxvor dem Caddy-Start ausführen. - „Error: loading modules: unknown module http.dns.cloudflare" – Das Standard-APT-Paket enthält keine DNS-Plugins. Lösung: Custom-Build mit xcaddy und
--with github.com/caddy-dns/cloudflare@latesterforderlich. - „no OCSP server specified in certificate" in den Logs – Let's Encrypt hat den OCSP-Dienst ab August 2025 eingestellt. Diese Warnung ist normal und kein Fehler; keine Aktion nötig.
- Falsche Client-IPs in Logs bei gestapelten Proxies – Ohne Konfiguration vertraut Caddy keinem vorgelagerten Proxy. Lösung: Im global block
servers { trusted_proxies static private_ranges }setzen. - ACME-Rate-Limit überschritten – Let's Encrypt erlaubt maximal 5 Zertifikate pro Domain pro 7 Tage. Für Tests Staging-CA nutzen: Im global block
acme_ca https://acme-staging-v02.api.letsencrypt.org/directorysetzen. - Kein Access-Log vorhanden – Caddy schreibt ohne explizite Konfiguration kein Request-Log. Lösung:
log { output file /var/log/caddy/access.log format json }im Site-Block oder global hinzufügen; Logverzeichnis mitsudo chown caddy:caddy /var/log/caddybeschreibbar machen. - Reload übernimmt Änderungen nicht – Syntaxfehler in der Caddyfile lässt den alten Prozess weiterlaufen. Lösung: immer zuerst
caddy validate --config /etc/caddy/Caddyfile, dann reload, dannsystemctl status caddyprüfen. - „urn:ietf:params:acme:error:unauthorized" bei DNS-Challenge – NTP-Synchronisation fehlt oder Zeitabweichung zu groß. Lösung:
timedatectl statusprüfen, systemd-timesyncd oder chrony sicherstellen. - Custom-Binary kann Port 80/443 nicht binden – Bei selbst kompilierten Binaries fehlt die Capability. Lösung:
sudo setcap cap_net_bind_service=+ep /usr/bin/caddyausführen.
Häufige Fragen
Brauche ich certbot noch, wenn ich Caddy nutze?
Nein. Caddy ersetzt certbot vollständig. Zertifikate werden automatisch von Let's Encrypt oder ZeroSSL bezogen, erneuert und per OCSP-Stapling verwaltet. Kein Cron-Job, kein manueller Eingriff nötig. Caddy erneuert Zertifikate frühzeitig mit exponentiellem Backoff und einem Retry-Fenster bis 30 Tage vor Ablauf. Detaillierte Hintergründe zur klassischen Zertifikatsverwaltung findest du in unserer certbot-Anleitung.
Wann sollte ich Traefik statt Caddy nehmen?
Traefik ist die bessere Wahl, wenn Docker- oder Kubernetes-Stacks mit dynamischer Service-Discovery im Einsatz sind: Traefik erkennt Container-Labels und rekonfiguriert sich automatisch. Caddy ist besser für Bare-Metal-Server mit stabiler App-Landschaft und minimalem Konfig-Aufwand.
Kann Caddy Wildcard-Zertifikate für *.intern.example.com ausstellen, ohne dass der Server öffentlich erreichbar ist?
Ja, sofern die DNS-Zone bei einem unterstützten Provider liegt (Cloudflare, Route53 usw.) und Caddy via API dorthin schreiben darf. Der Server selbst muss nicht vom Internet erreichbar sein – nur die DNS-Zone muss änderbar sein (DNS-01-Challenge). Das ist der empfohlene Weg für interne Netze.
Was passiert, wenn Let's Encrypt nicht erreichbar ist?
Caddy versucht automatisch ZeroSSL als Fallback-CA. Schlagen beide fehl, retried Caddy mit exponentiellem Backoff bis zu 30 Tage. Bestehende, noch gültige Zertifikate bleiben aktiv – der Server läuft weiter. Erst wenn das Zertifikat abläuft und kein Renewal gelang, treten TLS-Fehler auf.
Wie konfiguriere ich mehrere Apps unter verschiedenen Subdomains am effizientesten?
Entweder separate Site-Blöcke pro Subdomain (jeder bekommt ein eigenes Zertifikat) oder ein Wildcard-Block mit @matcher host subdomain.example.com-Regeln (ein einziges Wildcard-Zertifikat per DNS-Challenge). Für interne Netze ohne öffentliche Ports ist die Wildcard-plus-DNS-Challenge-Variante der empfohlene Weg.
Setzt Caddy Security-Header automatisch?
Nein. Caddy stellt automatisch HTTPS bereit und setzt X-Forwarded-For, X-Forwarded-Proto und X-Forwarded-Host für Upstream-Apps. Security-Header wie HSTS, X-Frame-Options oder X-Content-Type-Options müssen jedoch explizit über einen header { ... }-Block konfiguriert werden.
Fazit
Caddy löst ein echtes Problem: TLS-Verwaltung war bisher aufwändig, fehleranfällig und setzte mehrere Tools voraus. Mit Caddy schrumpft ein vollständiger, produktionsreifer Reverse-Proxy mit HTTPS, automatischem Renewal und Security-Headern auf wenige Zeilen Konfiguration. Der Preis dafür ist rund 15 % weniger Durchsatz gegenüber nginx und ein etwas höherer RAM-Bedarf – in den meisten Bare-Metal-Szenarien mit internen Apps ein fairer Tausch. Wer bereits auf Docker setzt, ist mit Traefik besser bedient; wer maximale Performance unter hoher Last braucht, bleibt bei nginx. Für alle anderen – Heimlabs, KMU-Server, interne Tool-Stacks – ist Caddy die pragmatischste Wahl.
Weiterführende Anleitungen und Quellen
- nginx als Reverse-Proxy mit TLS einrichten – die klassische Alternative mit maximaler Performance
- Traefik als Reverse-Proxy in Docker-Stacks – automatische Service-Discovery für Container-Umgebungen
- Let's Encrypt mit certbot – TLS-Zertifikate verwalten – wenn du bei nginx bleibst und certbot nutzen möchtest
- Linux-Server absichern mit UFW und fail2ban – Firewall-Konfiguration als Ergänzung zum Reverse-Proxy-Setup
Quellen: Caddy – Automatic HTTPS (offizielle Dokumentation), Caddy – reverse_proxy Directive, Caddy – Global Options / Caddyfile, CertMagic (GitHub), nginx vs. Caddy vs. Traefik Benchmark – HomelabSec.