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

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.

Modernes Serverrack mit blauem LED-Licht im Rechenzentrum

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:

KriteriumnginxCaddy v2Traefik v3
Durchsatz (statische Inhalte)45.230 req/s38.450 req/s (~15 % weniger)31.890 req/s
RAM idle~85 MB~120 MB~180 MB
RAM bei 50.000 WebSockets1,2 GB1,4 GB1,8 GB
Automatisches TLS (ohne certbot)NeinJaJa
HTTP→HTTPS-Redirect automatischNein (explizit)JaJa
Wildcard-ZertifikateVia certbot + CronJa (Custom-Build nötig)Ja
Docker-Service-DiscoveryNeinNeinJa (Labels)
Bare-Metal / stabiler App-StackGutOptimalMöglich
Config-AufwandHochMinimalMittel

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:

  1. Caddy auf einem anderen Port testen (z. B. 8080/8443) und Konfiguration validieren
  2. caddy validate --config /etc/caddy/Caddyfile ausführen – bei Fehler abbrechen
  3. nginx stoppen und deaktivieren: sudo systemctl stop nginx && sudo systemctl disable nginx
  4. Caddy starten: sudo systemctl start caddy – beim ersten Start holt Caddy sofort das Zertifikat (30–120 Sekunden Verzögerung normal)
  5. Status prüfen: sudo systemctl status caddy und sudo 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 nginx vor 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@latest erforderlich.
  • „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/directory setzen.
  • 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 mit sudo chown caddy:caddy /var/log/caddy beschreibbar 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, dann systemctl status caddy prüfen.
  • „urn:ietf:params:acme:error:unauthorized" bei DNS-Challenge – NTP-Synchronisation fehlt oder Zeitabweichung zu groß. Lösung: timedatectl status prü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/caddy ausfü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

Quellen: Caddy – Automatic HTTPS (offizielle Dokumentation), Caddy – reverse_proxy Directive, Caddy – Global Options / Caddyfile, CertMagic (GitHub), nginx vs. Caddy vs. Traefik Benchmark – HomelabSec.