Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Sicherheit & Datenschutz 04.11.2026 · 10 min Lesezeit

Caddy als Eingangstür: HTTPS, Sicherheits-Header und A+ bei SSL Labs

Teil 4 der Reihe Server im Netz: Caddy mit Docker als HTTPS-Eingangstür einrichten, vor und nach den Sicherheits-Headern bei SSL Labs, Mozilla Observatory und internet.nl gemessen. Mit der Docker-Falle, die bei IPv6 die echte Besucheradresse verschluckt.

Geprüft am 04.11.2026 · für Caddy 2.11.7, Docker 29.9.2, Ubuntu 24.04.5

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

Symbolbild: Server mit Netzkabel vor einer Weltkarte, Titel Caddy als Eingangstür

Sobald die Domain auf den Server zeigt, braucht er eine Eingangstür: einen Webserver, der Zertifikate selbst holt und erneuert, HTTP auf HTTPS umleitet und Besuchern nur das zeigt, was sie sehen sollen. Caddy erledigt das mit wenigen Zeilen Konfiguration. Diese Anleitung richtet Caddy mit Docker auf einem VPS ein und misst von außen, was die Grundeinstellung schon leistet und was die richtigen Sicherheits-Header bringen. Am Ende stehen A+ bei SSL Labs, A+ bei Mozilla Observatory und alle Sicherheitsoptionen grün bei internet.nl. Unterwegs zeigen wir eine Docker-Falle, die bei IPv6-Besuchern die echte Absender-Adresse verschluckt.

Dies ist Teil 4 der Reihe „Server im Netz“. Teil 3 hat die Domain sedv-lab.de mit A-, AAAA-, Wildcard- und CAA-Einträgen auf den Server gelegt. Als Beispiel dient ein netcup-VPS mit Ubuntu und Docker.

Voraussetzungen

  • Ein Linux-Server mit Docker und Docker Compose, siehe die verlinkte Grundlagen-Anleitung
  • Eine Domain, deren A- und AAAA-Einträge auf den Server zeigen, wie in Teil 3 beschrieben
  • Offene Ports 80 und 443 (TCP) sowie 443 (UDP) für HTTP/3, auf dem Server und in einer vorgeschalteten Firewall
  • Keine anderen Dienste auf den Ports 80 und 443

Schritt 1: Ordner und Compose-Datei anlegen

mkdir -p /opt/caddy/site /opt/caddy/log && cd /opt/caddy

Die compose.yaml:

services:
  caddy:
    image: caddy:2.11.7
    container_name: caddy
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./site:/srv/site:ro
      - ./log:/log
      - caddy_data:/data
      - caddy_config:/config
volumes:
  caddy_data:
  caddy_config:

Im Volume caddy_data liegen Zertifikate und Schlüssel. Sichern Sie es mit, sonst holt Caddy nach einem Neuaufbau alle Zertifikate neu.

Wichtig ist network_mode: host. Im Test haben wir Caddy zuerst klassisch mit ports: gestartet. Anfragen über IPv4 kamen mit echter Absender-Adresse an. Ein Aufruf, den unser Paketmitschnitt um 14:56:10 als IPv6-Verbindung von außen zeigt, stand im Caddy-Log dagegen mit 172.21.0.1, und auch die Prüfserver von Let's Encrypt erschienen mit dieser Adresse. Der Grund: Das Docker-Netz eines Compose-Projekts hat standardmäßig nur IPv4. Anfragen über IPv6 nimmt deshalb der Hilfsprozess docker-proxy an und reicht sie per IPv4 an den Container weiter. Der Container sieht dann die Gateway-Adresse des Docker-Netzes statt der Besucheradresse. Sperrlisten, Auswertungen und Werkzeuge wie CrowdSec arbeiten so mit der falschen Adresse. Im Host-Netz lauscht Caddy direkt auf den Ports des Servers, und das Problem verschwindet. Das war im Test die einfachste Lösung. Ein anderer Weg ist, IPv6 im Docker-Netz einzuschalten.

Was das Host-Netz bedeutet: Der Container nutzt den Netzwerkstapel des Servers mit, eine Netztrennung gibt es nicht mehr. Die Admin-Schnittstelle von Caddy lauscht dann auf 127.0.0.1:2019 des Servers, im Test so nachgesehen mit ss -tlnp. Jeder Prozess auf dem Server kann Caddy darüber ohne Anmeldung umkonfigurieren. Geben Sie diesen Port nie nach außen frei und lassen Sie auf dem Server nur Dienste laufen, denen Sie trauen. Ein Vorteil: Ports, die Docker mit ports: veröffentlicht, übergehen Firewall-Regeln wie die von UFW. Im Host-Netz gilt die Firewall für Caddy wie für jeden anderen Dienst.

Verifizieren: docker compose config --quiet meldet keinen Fehler.

Schritt 2: Das Caddyfile schreiben

Für den Anfang reicht eine kleine Startseite unter der Domain, eine Umleitung von www und ein Auffangblock für alles andere:

{
	email admin@sedv-lab.de
}

sedv-lab.de {
	root * /srv/site
	file_server
	encode zstd gzip
	log {
		output file /log/sedv-lab.json
		format json
	}
}

www.sedv-lab.de {
	redir https://sedv-lab.de{uri} permanent
}

# Aufrufe per IP und unbekannte Namen auf Port 80
:80 {
	log {
		output file /log/unbekannt.json
		format json
	}
	respond 404
}

Die E-Mail-Adresse oben nutzt Caddy für das Konto bei Let's Encrypt. Für jeden Block mit einem Domainnamen holt Caddy selbst ein Zertifikat und leitet HTTP auf HTTPS um. Der Block :80 fängt alles ab, was keinen bekannten Namen trägt, etwa Bots, die Server nur per IP abklappern. Sie bekommen eine leere 404, und ihre Anfragen landen in einem eigenen Log. Wegen der Wildcard aus Teil 3 landen auch ausgedachte Namen wie gibtsnicht.sedv-lab.de bei Ihrem Server: über HTTP bei diesem Block, über HTTPS bricht Caddy die Verbindung ab, weil es für den Namen kein Zertifikat hat.

Legen Sie noch eine einfache Startseite an, zum Beispiel site/index.html mit einer Überschrift.

Verifizieren: docker run --rm -v $PWD/Caddyfile:/etc/caddy/Caddyfile:ro caddy:2.11.7 caddy validate --config /etc/caddy/Caddyfile endet mit Valid configuration.

Schritt 3: Starten und das Zertifikat prüfen

docker compose up -d
docker logs caddy 2>&1 | grep -E "trying to solve challenge|certificate obtained"

Caddy nutzte im Test die Prüfung tls-alpn-01 über Port 443. Zwei Sekunden nach dem Start lief die Prüfung, bei unserer ersten Abfrage 25 Sekunden nach dem Start lieferte die Seite bereits HTTP/2 mit gültigem Zertifikat. Dieser erste Start lief noch im Bridge-Netz, nach dem Wechsel ins Host-Netz übernahm Caddy das Zertifikat aus dem Volume:

$ curl -sI https://sedv-lab.de | head -3
HTTP/2 200
alt-svc: h3=":443"; ma=2592000
content-type: text/html; charset=utf-8

Die Zeile alt-svc zeigt, dass Caddy auch HTTP/3 über UDP 443 anbietet. Das Zertifikat stammt von Let's Encrypt, gilt derzeit 90 Tage und wird von Caddy selbst erneuert.

Verifizieren: curl -sI http://sedv-lab.de liefert 308 Permanent Redirect auf HTTPS, curl -sI https://www.sedv-lab.de liefert 301 auf die Domain.

Schritt 4: Was die Grundeinstellung schon kann

Bevor wir etwas verbessern, messen wir. SSL Labs prüft Zertifikat, Protokolle und Verschlüsselung über IPv4 und IPv6:

SSL-Labs-Bericht für sedv-lab.de mit Note A vor den Sicherheits-Headern
SSL Labs vor den Sicherheits-Headern, Bericht für die IPv4-Adresse: Note A (1), CAA-Policy gefunden (2). Die Server-IP ist im Bild durch eine Beispieladresse ersetzt.

Ohne eine einzige TLS-Einstellung erreicht Caddy die Note A: nur TLS 1.2 und 1.3, Schlüsseltausch mit Post-Quanten-Verfahren, und die CAA-Policy aus Teil 3 wird erkannt. Zur Bestnote fehlte nur eines: HSTS. Mozilla Observatory, das die HTTP-Header bewertet, war deutlich strenger. Es gab die Note D mit 30 von 100 Punkten, vier von zwölf Tests schlugen fehl.

Verifizieren: Rufen Sie den SSL-Labs-Test für Ihre Domain auf und warten Sie, bis beide Adressen bewertet sind.

Schritt 5: Sicherheits-Header ergänzen

Die Header kommen als wiederverwendbarer Block an den Anfang des Caddyfiles, direkt nach dem globalen Block:

(sicher) {
	header {
		Strict-Transport-Security "max-age=31536000; includeSubDomains"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "DENY"
		Referrer-Policy "strict-origin-when-cross-origin"
		Permissions-Policy "camera=(), microphone=(), geolocation=()"
		Content-Security-Policy "default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'"
		Cross-Origin-Opener-Policy "same-origin"
		-Server
	}
}

In die Blöcke sedv-lab.de und www.sedv-lab.de kommt jeweils als erste Zeile import sicher. Was die Zeilen bewirken:

HeaderWirkung
Strict-Transport-SecurityDer Browser ruft die Domain ein Jahr lang nur noch über HTTPS auf, mit includeSubDomains auch alle Unternamen.
X-Content-Type-OptionsDer Browser rät keine Dateitypen, ein Bild bleibt ein Bild.
X-Frame-Options, frame-ancestorsKeine fremde Seite darf Ihre Seite in einem Rahmen einbetten.
Referrer-PolicyFremde Seiten erfahren nur die Domain, nicht den Pfad der Seite, von der ein Besucher kommt.
Permissions-PolicyKamera, Mikrofon und Standort sind für die Seite gesperrt.
Content-Security-PolicyInhalte nur vom eigenen Server, Bilder auch eingebettet als data:, Inline-Skripte und Inline-Stile gesperrt, Formulare senden nur an den eigenen Server.
Cross-Origin-Opener-PolicyTrennt das Fenster von fremden Seiten, die es geöffnet haben.
-ServerEntfernt den Header, der Caddy als Webserver nennt.

Zwei Hinweise vor dem Übernehmen: Mit includeSubDomains müssen alle Unternamen der Domain HTTPS können, auch Dienste im Heimnetz unter derselben Domain. Browser, die den Header einmal gesehen haben, öffnen sie sonst bis zu ein Jahr lang nicht mehr. Wer unsicher ist, beginnt mit max-age=300 und erhöht erst, wenn alle Unternamen HTTPS können. Und die Content-Security-Policy ist bewusst streng, weil die Testseite weder fremde Skripte noch Inline-Stile nutzt. Für eine echte Website mit Schriften oder Statistik von anderen Servern müssen Sie sie erweitern.

Neu laden ohne Unterbrechung:

docker exec caddy caddy reload --config /etc/caddy/Caddyfile

Verifizieren: curl -sI https://sedv-lab.de zeigt die neuen Header, der Header server fehlt.

Schritt 6: Neu messen

SSL-Labs-Bericht für sedv-lab.de mit Note A+ und HSTS
SSL Labs nach dem Einbau der Header: Note A+ (1) dank HSTS mit langer Laufzeit (2).

SSL Labs vergibt jetzt A+, für IPv4 und IPv6. Mozilla Observatory springt von D auf A+:

Mozilla HTTP Observatory mit Note A+ und 125 Punkten für sedv-lab.de
Mozilla HTTP Observatory nach dem Einbau der Header: Note A+ (1) mit 125 Punkten und allen zwölf Tests bestanden (2).

125 Punkte sind mehr als 100, weil Observatory Zusatzpunkte für besonders strenge Einstellungen vergibt. Als Empfehlung nennt es noch default-src 'none' statt 'self'. Das lohnt sich erst, wenn Sie genau wissen, welche Quellen Ihre Seite braucht.

Verifizieren: Beide Tests zeigen A+.

Schritt 7: security.txt und der Blick von internet.nl

internet.nl zeigte bei den Sicherheitsoptionen eine Warnung, weil eine security.txt fehlte, und zur Referrer-Policy einen Hinweis. Diese kleine Textdatei nach RFC 9116 sagt Sicherheitsforschern, wohin sie eine gefundene Lücke melden können. Legen Sie sie im Ordner der Website an:

mkdir -p /opt/caddy/site/.well-known
cat > /opt/caddy/site/.well-known/security.txt <<'EOF'
Contact: mailto:admin@sedv-lab.de
Expires: 2027-11-01T00:00:00.000Z
Preferred-Languages: de, en
Canonical: https://sedv-lab.de/.well-known/security.txt
EOF

Die Angaben Contact und Expires sind Pflicht. Das Ablaufdatum sollte weniger als ein Jahr in der Zukunft liegen. Tragen Sie sich eine Erinnerung ein, die Datei rechtzeitig zu erneuern. Die Kontaktadresse muss Mail empfangen können.

Website-Test von internet.nl mit allen Sicherheitsoptionen grün und fehlendem DNSSEC
Website-Test von internet.nl nach der security.txt: Die Domain ist nicht mit DNSSEC signiert (1), deshalb nur 80 Prozent. Alle Sicherheitsoptionen sind gesetzt (2).

Jetzt sind alle Sicherheitsoptionen grün. Die Gesamtnote von 80 Prozent liegt nur noch an DNSSEC. Die Testdomain ist nicht mit DNSSEC signiert, mehr dazu in Teil 3.

Verifizieren: curl -s https://sedv-lab.de/.well-known/security.txt liefert die Datei mit content-type: text/plain.

Typische Fehler

BeobachtungUrsache und Lösung
IPv6-Besucher erscheinen im Log als 172.x.0.1Caddy läuft mit ports: in einem Docker-Netz ohne IPv6, docker-proxy reicht IPv6 per IPv4 weiter. network_mode: host setzen oder IPv6 im Docker-Netz einschalten.
tlsv1 alert internal error beim Aufruf per IP oder unbekanntem Namen über HTTPSGewollt: Caddy hat für diesen Namen kein Zertifikat und bricht ab. Bekannte Namen bekommen ihre Seite.
failed to bind host port [::]:80/tcp: address already in usePort 80 ist schon belegt, im Test durch zwei Portangaben für denselben Port. Mit ports: "80:80" bindet Docker IPv4 und IPv6 gemeinsam, eine zweite Zeile für [::] ist überflüssig. Sonst mit ss -tlnp nachsehen, wer den Port belegt.
Unternamen im Heimnetz öffnen sich im Browser nicht mehrHSTS mit includeSubDomains. Dort HTTPS einrichten oder includeSubDomains weglassen, bevor Besucher den Header gesehen haben.
Observatory meldet fehlende Header trotz import sicherDer Block wurde nicht neu geladen oder import steht im falschen Block. caddy reload und mit curl -sI prüfen.
Änderungen am Caddyfile wirken nach caddy reload nichtManche Editoren speichern eine neue Datei, statt die alte zu überschreiben. Ein einzeln eingebundenes Caddyfile zeigt im Container dann noch die alte Fassung. Container neu starten oder statt der Datei den ganzen Ordner einbinden.

Häufige Fragen

Warum Caddy und nicht nginx oder Traefik?

Caddy holt und erneuert Zertifikate ohne Zusatzwerkzeug und hat sichere TLS-Vorgaben eingebaut, die im Test schon ohne Anpassung die Note A brachten. Ein Vergleich mit nginx und Traefik ist unten verlinkt.

Sollte ich meine Domain in die HSTS-Preload-Liste eintragen?

Nur, wenn Sie sicher sind, dass die Domain dauerhaft und mit allen Unternamen HTTPS kann. Dafür bräuchte der Header zusätzlich die Angabe preload, und der Eintrag ist schwer rückgängig zu machen. Für eine Testdomain wie diese lassen wir es.

Wie oft erneuert Caddy das Zertifikat?

Caddy kümmert sich selbst darum und erneuert rechtzeitig vor dem Ablauf. Das Volume caddy_data muss dafür erhalten bleiben.

Was ist mit Fail2ban oder CrowdSec?

Beides baut auf den Logs auf, deshalb ist die echte Absender-Adresse so wichtig. CrowdSec mit Caddy richten wir in einem späteren Teil der Reihe ein.

Fazit

Caddy liefert mit Docker in wenigen Minuten eine saubere HTTPS-Eingangstür: Zertifikat in unter einer halben Minute, Umleitung auf HTTPS, HTTP/3 und eine A-Note bei SSL Labs ohne jede TLS-Einstellung. Mit sieben Sicherheits-Headern und einer security.txt wird daraus A+ bei SSL Labs und Observatory, und bei internet.nl sind alle Sicherheitsoptionen grün. Die wichtigste Erkenntnis aus dem Test hat mit TLS nichts zu tun: Im Bridge-Netz von Docker verlieren Sie bei IPv6-Besuchern die echte Adresse, im Host-Netz nicht. Im nächsten Teil bekommt sedv-lab.de eine echte Website.

Weiterführende Anleitungen und Quellen

Server im NetzCaddyHTTPSHSTSSicherheits-HeaderDockerSSL Labs