Caddy – Konzepte und Konfiguration
Caddy – Konzepte und Konfiguration
Was macht ein Webserver?
Ein Webserver wartet auf eingehende Netzwerkanfragen (HTTP/HTTPS) und beantwortet sie. Das kann bedeuten:
- Eine Datei ausliefern (HTML, Bild, CSS)
- Eine Anfrage an einen anderen Dienst weiterzuleiten (Reverse Proxy)
- Eine Anfrage umzuleiten (Redirect)
- Eine Anfrage abzulehnen (403, 404)
Klassische Webserver wie Apache und nginx können all das – benötigen aber für HTTPS externe Werkzeuge (z.B. Certbot) und manuelle Zertifikatsverwaltung.
Caddy übernimmt die Zertifikatsverwaltung automatisch: Er bezieht TLS-Zertifikate von Let's Encrypt, erneuert sie selbstständig, und erzwingt HTTPS ohne manuellen Eingriff.
Das Caddyfile
Die Konfiguration von Caddy erfolgt in einer einzigen Textdatei: dem Caddyfile. Die Syntax ist bewusst einfach gehalten.
Grundstruktur:
example.com {
# Direktiven für diese Domain
}
Alles innerhalb der geschweiften Klammern gilt nur für diese Domain. Mehrere Domains können in derselben Datei konfiguriert werden.
Direktiven
reverse_proxy
Kurzfassung: Leitet eingehende Anfragen an einen anderen Dienst weiter.
reverse_proxy localhost:8080 reverse_proxy myapp:3000
Erklärung:
Ein Reverse Proxy sitzt zwischen dem Browser des Nutzers und dem eigentlichen Dienst. Der Browser spricht immer mit Caddy – nie direkt mit dem Dienst dahinter. Caddy nimmt die Anfrage entgegen, leitet sie weiter, bekommt die Antwort, und schickt sie zurück an den Browser.
Vorteile:
- Nur Caddy ist nach außen sichtbar – interne Dienste bleiben verborgen
- TLS wird einmalig bei Caddy terminiert
- Mehrere Dienste können hinter einer einzigen IP laufen
Bei Docker-Containern kann der Containername als Hostname verwendet werden:
reverse_proxy nextcloud:443
Caddy löst nextcloud über das interne Docker-Netz auf.
transport http
Kurzfassung: Steuert wie Caddy intern mit dem Zieldienst kommuniziert.
reverse_proxy nextcloud:443 {
transport http {
tls_insecure_skip_verify
}
}
Erklärung:
Standardmäßig kommuniziert Caddy mit Zieldiensten über HTTP. Spricht der Zieldienst selbst HTTPS (z.B. ein Container der intern TLS verwendet), muss Caddy das wissen – und bekommt den transport http-Block.
tls_insecure_skip_verify deaktiviert die Zertifikatsprüfung für die interne Verbindung. Das klingt unsicher – ist es intern aber nicht, weil:
- Die Verbindung innerhalb des Docker-Netzes stattfindet
- Kein externer Angreifer Zugriff auf diesen internen Kanal hat
- Das Zertifikat des Zieldienstes selbstsigniert sein darf
Nach außen (Browser → Caddy) bleibt HTTPS mit gültigem Zertifikat bestehen.
header
Kurzfassung: Setzt, entfernt oder verändert HTTP-Response-Header – also Header die Caddy an den Browser zurückschickt.
header {
-Server
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "no-referrer"
}
Erklärung:
HTTP-Header sind Metadaten die jede Antwort begleiten. Sie sagen dem Browser wie er die Antwort behandeln soll.
-Server– Das Minuszeichen entfernt den Header. Ohne diese Zeile würde CaddyServer: Caddymitsenden – ein Hinweis für Angreifer welche Software läuft. Konkret: Caddy würde normalerweise mitteilen: "Hallo, ich bin Caddy v2.10.2". Der Minus löscht diesen Header. Der Besucher sieht nichts.Strict-Transport-Security– Sagt dem Browser: „Verbinde dich für die nächsten 31536000 Sekunden (1 Jahr) ausschließlich über HTTPS mit dieser Domain." Konkret: Sagt dem Browser: "Diese Seite gibt es nur per HTTPS, für die nächsten 365 Tage – frag gar nicht erst per HTTP an."X-Content-Type-Options "nosniff"– Verhindert dass der Browser den Dateityp einer Antwort „errät". Schutz gegen bestimmte Angriffe über präparierte Dateien. Konkret: Klassischer Angriffsvektor: Server schickt eine HTML-Datei als text/plain, Browser erkennt sie trotzdem als HTML und führt JavaScript aus.X-Frame-Options "SAMEORIGIN"– Verhindert dass die Seite in einem<iframe>einer fremden Domain eingebettet wird. Schutz gegen Clickjacking. Konkret: Jemand baut deine Seite unsichtbar in seine ein und du klickst unwissentlich auf deren Buttons.Referrer-Policy "no-referrer"– Der Browser sendet beim Klick auf einen Link keine Information woher der Nutzer kam. Konkret: Die externe Seite erfährt nicht, woher du kommst.
Diese Header bilden gemeinsam eine erste Verteidigungslinie: Jeder schützt gegen eine andere Angriffsart, jeder unabhängig vom anderen. Das Prinzip dahinter heißt Defense in Depth – Sicherheit durch mehrere unabhängige Schichten. Fällt eine aus, stehen die anderen noch. 🏰
header_up
Kurzfassung: Setzt Header die Caddy an den Zieldienst (upstream) weiterleitet – nicht an den Browser.
reverse_proxy myapp:8080 {
header_up X-Real-IP {remote_ip}
}
Erklärung:
Ohne zusätzliche Header sieht ein Dienst hinter Caddy als Absender immer Caddys interne IP – nie die echte Client-IP. Mit header_up X-Real-IP {remote_ip} reicht Caddy die echte IP des Clients weiter.
{remote_ip} ist eine Caddy-Variable die zur Laufzeit durch die tatsächliche IP ersetzt wird.
Caddy setzt X-Forwarded-For, X-Forwarded-Proto und X-Forwarded-Host automatisch – diese müssen nicht manuell gesetzt werden.
@matcher (Named Matchers)
Kurzfassung: Definiert eine benannte Bedingung die auf Anfragen zutreffen kann oder nicht.
@xmlrpc path /xmlrpc.php
respond @xmlrpc 403
@admin {
method POST
path /admin/*
}
handle @admin {
reverse_proxy backend:8080
}
Erklärung:
Matcher sind das Herzstück von Caddy-Konfigurationen. Sie ermöglichen es, Regeln nur auf bestimmte Anfragen anzuwenden.
Das @-Zeichen leitet einen Matcher ein. Der Name danach ist frei wählbar. Matcher können prüfen:
path– welcher Pfad wird angefragt?method– GET, POST, PUT, DELETE...?query– enthält die URL bestimmte Parameter?remote_ip– kommt die Anfrage von einer bestimmten IP?header– enthält die Anfrage bestimmte Header?
Mehrere Bedingungen in einem Block werden mit UND verknüpft – alle müssen zutreffen.
handle
Kurzfassung: Führt Direktiven nur dann aus wenn ein Matcher zutrifft. Nicht-passende Anfragen fallen durch zum nächsten Block.
@static path /static/*
handle @static {
file_server
}
handle {
reverse_proxy backend:8080
}
Erklärung:
handle ohne Matcher ist der Standardfall – er greift für alles was kein anderer Block behandelt hat. In Kombination mit Matchern entsteht eine Art Weiche: statische Dateien werden direkt ausgeliefert, alles andere geht an den Backend-Dienst.
route
Kurzfassung: Wie handle, aber die Reihenfolge der Direktiven wird strikt eingehalten.
Erklärung:
Caddy verarbeitet Direktiven normalerweise in einer intern festgelegten Reihenfolge – unabhängig davon wie sie im Caddyfile stehen. route erzwingt die Reihenfolge wie sie im Caddyfile steht. Nützlich wenn die Reihenfolge der Verarbeitung wichtig ist.
redir
Kurzfassung: Leitet den Browser auf eine andere URL um.
redir https://new.example.com{uri} 301
redir https://new.example.com{uri} 308
Erklärung:
Der Unterschied zwischen 301 und 308:
| Code | Name | Bedeutung |
|---|---|---|
| 301 | Moved Permanently | Browser darf die HTTP-Methode ändern (POST → GET) |
| 308 | Permanent Redirect | Browser behält die HTTP-Methode bei (POST bleibt POST) |
Für einfache Browser-Weiterleitungen ist 301 der Klassiker. Für API-Weiterleitungen oder Formulare ist 308 wichtig.
{uri} ist eine Caddy-Variable für den vollständigen Pfad inkl. Query-String – so bleibt der ursprüngliche Pfad nach der Weiterleitung erhalten.
respond
Kurzfassung: Antwortet direkt mit einem HTTP-Statuscode – ohne den Zieldienst zu kontaktieren.
@xmlrpc path /xmlrpc.php respond @xmlrpc 403 respond 404
Erklärung:
Nützlich um bestimmte Pfade hart zu blockieren ohne dass die Anfrage den eigentlichen Dienst erreicht. Caddy antwortet sofort – schnell und ressourcenschonend.
Gängige Statuscodes:
| Code | Bedeutung |
|---|---|
| 200 | OK |
| 301/308 | Weiterleitung (permanent) |
| 403 | Forbidden – Zugriff verweigert |
| 404 | Not Found – nicht vorhanden |
| 500 | Internal Server Error |
file_server
Kurzfassung: Liefert statische Dateien aus einem Verzeichnis aus.
root * /var/www/html file_server
Erklärung:
root legt das Basisverzeichnis fest. file_server aktiviert die Auslieferung. Caddy sucht dann für jeden Request die entsprechende Datei im Verzeichnis und liefert sie aus – wie ein klassischer Webserver.
tls
Kurzfassung: Steuert TLS-Einstellungen für eine Domain.
tls {
alpn http/1.1
}
Erklärung:
Caddy verwaltet TLS-Zertifikate automatisch. Der tls-Block ist nur nötig wenn vom Standard abgewichen werden soll.
alpn (Application-Layer Protocol Negotiation) legt fest welche HTTP-Version ausgehandelt wird. Standardmäßig bietet Caddy HTTP/2 an. Mit alpn http/1.1 wird HTTP/2 deaktiviert – nützlich wenn ein Zieldienst damit nicht zurechtkommt.
Globale Optionen
Am Anfang des Caddyfiles können globale Einstellungen gesetzt werden:
{
admin 127.0.0.1:2019
}
admin aktiviert die Caddy-Admin-API auf dem angegebenen Port. Mit 127.0.0.1:2019 ist sie nur lokal erreichbar – nicht von außen. Über diese API kann Caddy zur Laufzeit neu geladen werden:
<syntaxhighlight lang="bash"> caddy reload --config /etc/caddy/Caddyfile
Abschluss-"Meditation"
Auch, wenn's in diesem Beitrag "nur" um Caddy selbst geht, ist der Webserver dennoch mit allen anderen Schichten verbunden. Die Komplexität eines Webservers kommt daher dass jede dieser Schichten eine eigene Sprache spricht:
Docker denkt in Containern und Netzwerken Caddy denkt in Requests und Routen Collabora denkt in WOPI und Aliases Der Browser denkt in Headers und Policies
Die sprechen alle miteinander – aber keiner erklärt dem anderen was er tut. Die Übersetzung geschieht, bei einer Realisation über Docker-Container, im Zusammenspiel von Kombination aus docker-compose.yml und Caddyfile. Das ist das Schwierige.
Die beiden müssen zusammenpassen:
Compose definiert wer existiert und wie die Container heißen Caddy nutzt genau diese Namen als interne Adressen.
Beispiel (yml):
container_name: myAwesomeContainer
Im Caddyfile spricht Caddy myAwesomeContainer mit diesem Namen an:
reverse_proxy myAwesomeContainer:12345
(-> 12345 = Portnummer, unter der der Container erreichbar ist)
Docker's internes DNS löst myAwesomeContainer zur richtigen IP auf – automatisch. Das ist die unsichtbare Brücke zwischen beiden Files.
Und das gemeinsame Docker-Netzwerk ist der Raum in dem alle miteinander reden dürfen. Wer nicht drin ist, hört nichts. 🏰
Siehe auch
- Webserver, Reverse Proxy und Container – wie die Schichten zusammenspielen – Konzepte hinter Reverse Proxy, Pfad-Whitelist, Security-Header und Container-Netzwerken
- Hairpin-NAT – Collabora und Nextcloud hinter Caddy
- Caddy-Dokumentation (englisch)
- Caddy-Direktiven (englisch)
