Caddy — Webserver mit automatischem HTTPS

Praxis-Guide zu Caddy — automatische TLS-Zertifikate, Caddyfile-Syntax, Reverse Proxy, statischer Dateiserver und CLI, mit Beispielen für den Alltag.

Caddy ist ein moderner, in Go geschriebener Webserver, der dir das lästigste Detail im Web-Betrieb abnimmt: Er stellt TLS-Zertifikate über Let's Encrypt oder ZeroSSL vollautomatisch aus und erneuert sie, ohne dass du certbot oder Cronjobs anfassen musst. HTTP/2 und HTTP/3 sind standardmäßig aktiv, und die übersichtliche Caddyfile-Syntax bringt dich mit wenigen Zeilen von der leeren Datei zur produktiven Konfiguration. Ob statischer Dateiserver, Reverse Proxy oder PHP-Backend – Caddy deckt die typischen Aufgaben mit klaren Direktiven ab. Dieser Guide führt dich durch die wichtigsten CLI-Befehle und Caddyfile-Bausteine für den Alltag.

Service-Verwaltung (systemctl)

systemctl start caddy — Startet den Caddy-Webserver.

systemctl start caddy

systemctl stop caddy — Stoppt den Caddy-Webserver.

systemctl stop caddy

systemctl restart caddy — Startet Caddy neu (unterbricht kurz die Verbindungen). Für Config-Änderungen ohne Ausfallzeit reload verwenden.

systemctl restart caddy

systemctl reload caddy — Lädt die Caddy-Konfiguration ohne Ausfallzeit neu. Caddy liest /etc/caddy/Caddyfile erneut ein.

systemctl reload caddy

systemctl status caddy — Zeigt Status, PID und letzte Log-Ausgabe des Caddy-Dienstes.

systemctl status caddy

systemctl enable caddy — Aktiviert den automatischen Start von Caddy beim Systemstart.

systemctl enable caddy

systemctl disable caddy — Deaktiviert den automatischen Start von Caddy beim Booten.

systemctl disable caddy

caddy-CLI-Befehle

caddy run — Startet Caddy im Vordergrund mit der Caddyfile im aktuellen Verzeichnis. Logs gehen nach stdout.

caddy run

caddy run --config <file> — Startet Caddy im Vordergrund mit einer bestimmten Konfigurationsdatei.

caddy run --config /etc/caddy/Caddyfile

caddy start — Startet Caddy als Hintergrund-Daemon.

caddy start

caddy start --config <file> — Startet Caddy als Hintergrund-Daemon mit einer bestimmten Konfigurationsdatei.

caddy start --config /etc/caddy/Caddyfile

caddy stop — Stoppt den laufenden Caddy-Hintergrund-Daemon.

caddy stop

caddy reload — Lädt die Caddy-Konfiguration ohne Ausfallzeit neu. Übernimmt die Änderungen aus der Caddyfile.

caddy reload

caddy reload --config <file> — Lädt Caddy mit einer bestimmten Konfigurationsdatei neu.

caddy reload --config /etc/caddy/Caddyfile

caddy validate --config <file> — Prüft eine Caddyfile auf Syntax- und Konfigurationsfehler, ohne den Server zu starten.

caddy validate --config /etc/caddy/Caddyfile

caddy fmt <file> — Formatiert eine Caddyfile nach dem kanonischen Stil.

caddy fmt /etc/caddy/Caddyfile

caddy fmt --overwrite <file> — Formatiert eine Caddyfile und überschreibt sie direkt.

caddy fmt --overwrite /etc/caddy/Caddyfile

caddy version — Zeigt die installierte Caddy-Version.

caddy version

caddy list-modules — Listet alle verfügbaren Caddy-Module auf (Direktiven, Handler, Matcher usw.).

caddy list-modules

caddy upgrade — Aktualisiert Caddy auf die neueste Version (ersetzt die Binary).

caddy upgrade

caddy adapt --config <file> — Wandelt eine Caddyfile in Caddys natives JSON-Konfigurationsformat um.

caddy adapt --config Caddyfile

caddy environ — Gibt die Umgebungsvariablen aus, die Caddy zur Laufzeit verwendet.

caddy environ

Caddyfile: Grundstruktur

Minimaler Site-Block. Caddy stellt automatisch ein TLS-Zertifikat für die Domain aus.

example.com {
    respond "Hello, World!"
}

Bedient mehrere Domains aus demselben Block (kommagetrennt).

example.com, www.example.com {
    # shared config
}

Globaler Optionsblock (muss der erste Block sein). Legt die ACME-E-Mail, On-Demand-TLS und weitere globale Einstellungen fest.

{
    email admin@example.com
    on_demand_tls {
        ask http://localhost:9000/check
    }
}

Definiert ein wiederverwendbares Konfigurations-Snippet und importiert es in Site-Blöcken.

(common) {
    encode gzip zstd
    header X-Frame-Options DENY
}
example.com {
    import common
}

Lauscht auf einem bestimmten Port ohne HTTPS (kein TLS bei reinen Port-Adressen).

:8080 {
    respond "dev server"
}

Nutzt Caddys eingebaute interne CA für ein lokal vertrauenswürdiges TLS-Zertifikat (ohne Internet).

localhost {
    tls internal
    respond "local dev"
}

Statischer Dateiserver

Bedient statische Dateien aus einem Verzeichnis. Das * matcht alle Requests.

example.com {
    root * /var/www/html
    file_server
}

Bedient statische Dateien mit aktiviertem Verzeichnis-Listing.

example.com {
    root * /var/www/html
    file_server browse
}

SPA-Modus: probiert den exakten Pfad und fällt für clientseitiges Routing auf index.html zurück.

example.com {
    root * /var/www/html
    try_files {path} /index.html
    file_server
}

Verbirgt bestimmte Dateien oder Verzeichnisse, sodass sie nicht ausgeliefert werden.

file_server {
    hide .git
    hide *.secret
}

caddy file-server --root ./public --listen :8080 — Bedient ein lokales Verzeichnis sofort von der Kommandozeile aus – ganz ohne Caddyfile.

caddy file-server --root ./dist --listen :3000

Reverse Proxy

Leitet alle Requests einer Domain an ein lokales Backend auf Port 3000 weiter.

example.com {
    reverse_proxy localhost:3000
}

Leitet nur Requests mit passendem Pfad-Präfix an ein bestimmtes Backend weiter.

example.com {
    reverse_proxy /api/* localhost:4000
}

Verteilt die Last standardmäßig per Round-Robin auf mehrere Backends.

example.com {
    reverse_proxy backend1:8080 backend2:8080
}

Verteilt die Last nach der Least-Connections-Strategie.

reverse_proxy localhost:3000 {
    lb_policy least_conn
}

Reicht die ursprünglichen Host- und Client-IP-Header an das Upstream-Backend weiter.

reverse_proxy localhost:3000 {
    header_up Host {upstream_hostport}
    header_up X-Real-IP {remote_host}
    header_up X-Forwarded-For {remote_host}
}

Konfiguriert aktive Health-Checks für das Upstream-Backend.

reverse_proxy localhost:3000 {
    health_uri /healthz
    health_interval 10s
    health_timeout 2s
}

Leitet an ein HTTPS-Upstream weiter und überspringt dabei die TLS-Prüfung (für interne selbstsignierte Zertifikate).

reverse_proxy https://upstream.example.com {
    transport http {
        tls_insecure_skip_verify
    }
}

Weiterleitungen & Rewrites

Leitet www dauerhaft auf die Apex-Domain um und behält die Request-URI bei.

www.example.com {
    redir https://example.com{uri} permanent
}

Leitet allen HTTP-Verkehr auf HTTPS um. Caddy erledigt das normalerweise automatisch.

http://example.com {
    redir https://example.com{uri}
}

redir /old-path /new-path permanent — Leitet einen bestimmten Pfad dauerhaft an einen neuen Ort um (301).

redir /blog /news permanent

redir /old-path /new-path temporary — Leitet einen Pfad temporär um (302).

redir /sale /deals temporary

rewrite /app/* /index.html — Schreibt passende Requests intern auf einen anderen Pfad um (ohne Browser-Weiterleitung).

rewrite /app/* /index.html

rewrite * /index.php{query} — Schreibt alle Requests auf index.php um und behält den Query-String bei (nützlich für PHP-Apps).

rewrite * /index.php{query}

TLS / HTTPS

Caddy stellt bei einer öffentlichen Domain automatisch ein Let's-Encrypt-Zertifikat aus. Keine zusätzliche Konfiguration nötig.

example.com {
    # TLS is automatic — no config needed
}

tls admin@example.com — Setzt explizit die ACME-Registrierungs-E-Mail für diesen Site-Block (überschreibt die globale Einstellung).

tls admin@example.com

tls /path/to/cert.pem /path/to/key.pem — Nutzt ein eigenes (manuell verwaltetes) TLS-Zertifikat samt privatem Schlüssel.

tls /etc/ssl/certs/example.crt /etc/ssl/private/example.key

tls internal — Nutzt Caddys eingebaute lokale CA zur Zertifikatsausstellung. Auf der lokalen Maschine automatisch vertrauenswürdig.

localhost { tls internal }

Schränkt TLS-Protokollversionen und Cipher-Suites ein.

tls {
    protocols tls1.2 tls1.3
    ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
}

Nutzt die DNS-01-ACME-Challenge (nötig für Wildcard-Zertifikate oder nicht öffentlich erreichbare Server).

tls {
    dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}

caddy trust — Installiert Caddys lokale Root-CA in die Trust-Stores von System und Browser (für localhost-TLS).

caddy trust

caddy untrust — Entfernt Caddys lokale Root-CA aus den Trust-Stores.

caddy untrust

Header & Komprimierung

encode gzip zstd — Aktiviert gzip- und zstd-Komprimierung der Antworten. Caddy wählt das beste vom Client unterstützte Format.

encode gzip zstd

encode zstd gzip — Aktiviert Komprimierung und bevorzugt zstd vor gzip.

encode zstd gzip

header X-Frame-Options DENY — Setzt einen eigenen Response-Header.

header X-Frame-Options DENY

header -Server — Entfernt einen Response-Header. Das Minus-Präfix löscht den Header.

header -Server

Setzt mehrere Security-Header und entfernt den Server-Header in einem Block.

header {
    X-Content-Type-Options nosniff
    X-Frame-Options DENY
    Referrer-Policy strict-origin-when-cross-origin
    -Server
}

header Cache-Control "public, max-age=31536000, immutable" — Setzt langfristige Caching-Header für statische Assets.

header Cache-Control "public, max-age=31536000"

header Access-Control-Allow-Origin * — Erlaubt alle Origins für CORS (Cross-Origin Resource Sharing).

header Access-Control-Allow-Origin *

PHP & FastCGI

Bedient eine PHP-Anwendung via PHP-FPM über einen Unix-Socket. Kombiniert FastCGI, try_files und file_server.

example.com {
    root * /var/www/html
    php_fastcgi unix//run/php/php8.3-fpm.sock
    file_server
}

php_fastcgi 127.0.0.1:9000 — Verbindet sich mit PHP-FPM über TCP statt über einen Unix-Socket.

php_fastcgi 127.0.0.1:9000

Komplettes WordPress-Setup mit PHP-FPM, statischen Dateien und Komprimierung.

example.com {
    root * /var/www/wordpress
    php_fastcgi unix//run/php/php8.3-fpm.sock
    file_server
    encode gzip zstd
}

Matcher & Routing

Leitet /api/* an ein Backend und alles andere an den Dateiserver.

handle /api/* {
    reverse_proxy localhost:3000
}
handle {
    file_server
}

Wie handle, entfernt aber das passende Pfad-Präfix, bevor es an den inneren Block übergeben wird.

handle_path /static/* {
    root * /var/www/static
    file_server
}

Definiert einen benannten Matcher mit @name und referenziert ihn in einer Direktive.

@api path /api/*
reverse_proxy @api localhost:3000

Matcht Requests anhand eines Header-Werts und liefert für Bots einen 403 zurück.

@bot header User-Agent *bot*
respond @bot 403

Kombiniert Matcher: wendet Basic Auth nur auf /admin/* eines bestimmten Hosts an.

@secure {
    host example.com
    path /admin/*
}
basicauth @secure {
    admin JDJhJDE0JG...
}

Nutzt den 'not'-Matcher, um alle außer www auf die www-Domain umzuleiten.

@notwww not host www.example.com
redir @notwww https://www.example.com{uri}

Logging

log — Aktiviert Access-Logging mit Standardwerten (JSON-Format nach stderr).

log

Schreibt Access-Logs in eine Datei.

log {
    output file /var/log/caddy/access.log
}

Access-Log mit automatischer Rotation: max. 100 MB pro Datei, 5 Dateien behalten, max. 30 Tage.

log {
    output file /var/log/caddy/access.log {
        roll_size 100mb
        roll_keep 5
        roll_keep_for 720h
    }
}

Nutzt das menschenlesbare Konsolen-Format statt JSON für die Log-Ausgabe.

log {
    format console
}

Loggt nach stdout im JSON-Format mit Debug-Level (nützlich in Containern).

log {
    output stdout
    format json
    level DEBUG
}

journalctl -u caddy -f — Verfolgt Caddys Live-Log-Ausgabe über das systemd-Journal.

journalctl -u caddy -f

journalctl -u caddy --since "1 hour ago" — Zeigt Caddy-Logs der letzten Stunde.

journalctl -u caddy --since "1 hour ago"

Admin-API

curl -s http://localhost:2019/config/ — Holt die aktuelle Caddy-Konfiguration als JSON über die Admin-API (Standard-Listen: localhost:2019).

curl -s http://localhost:2019/config/ | jq '.'

curl -X POST http://localhost:2019/load -H 'Content-Type: text/caddyfile' --data-binary @Caddyfile — Lädt eine Caddyfile über die Admin-API (Reload ohne Ausfallzeit).

curl -X POST http://localhost:2019/load -H 'Content-Type: text/caddyfile' --data-binary @Caddyfile

curl -X DELETE http://localhost:2019/config/ — Leert die aktuelle Caddy-Konfiguration über die Admin-API.

curl -X DELETE http://localhost:2019/config/

curl -s http://localhost:2019/reverse_proxy/upstreams — Listet alle Reverse-Proxy-Upstream-Backends und ihren Health-Status auf.

curl -s http://localhost:2019/reverse_proxy/upstreams | jq '.'

Deaktiviert die Admin-API komplett im globalen Optionsblock (Härtung für die Produktion).

{
    admin off
}

Ändert die Listen-Adresse der Admin-API im globalen Optionsblock.

{
    admin localhost:2020
}

Praxisbeispiele

Produktionsreife statische Site mit Komprimierung und Security-Headern.

example.com {
    root * /var/www/html
    encode gzip zstd
    file_server
    header -Server
    header X-Frame-Options DENY
}

Reverse Proxy auf eine Node.js- oder andere Backend-App mit Komprimierung und gehärteten Headern.

app.example.com {
    encode gzip zstd
    reverse_proxy localhost:3000
    header -Server
}

Standard-PHP-Site mit PHP-FPM, statischem Dateiserver und gzip.

example.com {
    root * /var/www/html
    encode gzip
    php_fastcgi unix//run/php/php8.3-fpm.sock
    file_server
}

Wildcard-Zertifikat per DNS-Challenge, das Subdomains an verschiedene Backends routet.

*.example.com {
    tls {
        dns cloudflare {env.CF_TOKEN}
    }
    @app host app.example.com
    handle @app {
        reverse_proxy localhost:3000
    }
}

caddy reverse-proxy --from :80 --to localhost:3000 — Startet sofort einen Reverse Proxy von der Kommandozeile aus – ganz ohne Caddyfile.

caddy reverse-proxy --from :443 --to localhost:3000

Liefert für einen Health-Check-Endpunkt einen statischen 200 und leitet alles andere weiter.

example.com {
    respond /healthz 200
    reverse_proxy localhost:3000
}

Fazit

Caddy verschiebt die Messlatte für komfortablen Web-Betrieb: Was bei klassischen Servern manuelle Zertifikatsverwaltung und seitenlange Konfiguration bedeutet, erledigt Caddy mit sinnvollen Defaults und automatischem HTTPS. Damit ein neues Setup reibungslos startet, gilt eine einfache Routine – vor jedem reload mit caddy validate prüfen und mit caddy fmt formatieren. Für automatisches HTTPS müssen außerdem die Ports 80 und 443 offen und die Domain öffentlich erreichbar sein, sonst kann Caddy die ACME-Challenge nicht abschließen.

Verwandte Kommandos

  • apache – der etablierte, modular aufgebaute Webserver-Klassiker
  • certbot – TLS-Zertifikate manuell via Let's Encrypt verwalten
  • ferron – moderner, ressourcenschonender Webserver als Alternative