TLS / Zertifikate verstehen und debuggen
Du öffnest /certificates in der Admin-UI und siehst eine Tabelle deiner
Domains mit Status-Tags. Eine Domain zeigt "HTTP ohne TLS" wo du
eigentlich HTTPS erwartest. Oder HTTPS funktioniert obwohl du
https_enabled=false gesetzt hast. Oder ein Cert für eine frische
Route kommt einfach nicht an.
Diese Anleitung erklärt, was unter der Haube passiert, wie du die Zertifikate-Seite richtig liest und was du bei den typischen Problemen tun kannst.
Wie TLS in GateControl wirklich funktioniert
Caddy ist der Cert-Manager
Jede HTTP-Route wird als Domain an Caddy weitergereicht. Caddy nutzt sein eingebautes Automatic HTTPS und holt für jede Domain, die er serviert, automatisch ein Let's-Encrypt-Zertifikat — HTTP-01-Challenge über Port 80.
Die relevanten Environment-Hooks:
GC_CADDY_EMAIL— Kontakt-Email für Let's Encrypt. Ohne Email bekommst du keine Expiry-Warnungen von LE.GC_CADDY_ACME_CA— optionale Custom-ACME-Endpoint-URL. Default ist LE Production. Für Dev/Test setzbar auf LE Staging (https://acme-staging-v02.api.letsencrypt.org/directory) oder auf eine private CA (Step-CA, Smallstep, Vault). Das ist dasacme_custom_ca-Feature.
Alle HTTP-Routen teilen sich einen Server-Block
Wichtiges Implementierungsdetail: src/services/caddyConfig.js fasst
alle Domains mit HTTP-Route in einen einzigen Caddy-Server srv0
zusammen (siehe Code um Zeile 626–635). Dieser eine Server lauscht auf
:443 und :80. Per-Route-Listen-Konfiguration wird dabei verworfen.
Die https_enabled-Spalte in der Routen-Tabelle existiert noch, hat aber
auf Cert-Issuance und Listener-Binding keine Wirkung. Sie war
historisch als Per-Route-Listener-Toggle gedacht, wurde aber vom
"alles-in-srv0-zusammenführen"-Refactor überholt. In der Code-Flussrichtung
bleibt das Feld zwar gesetzt, aber die einzige Stelle die früher darauf
geachtet hat (listen: https_enabled ? [':443'] : [':80'] in einzelnen
Server-Blocks) wird durch den nachfolgenden Merge in srv0 ersetzt.
Praktische Konsequenz: Jede HTTP-Route mit Domain bekommt
automatisch einen Auto-TLS-Cert — egal was https_enabled sagt. Wenn du
HTTPS wirklich unterbinden willst, müsstest du den Caddy-Template-Code
patchen; davon raten wir ab.
L4-Routen — drei TLS-Modi
L4-Routen laufen nicht durch Caddy's HTTP-Stack, sondern durch Caddy-L4. Deswegen gelten andere Regeln:
tls_mode=none— purer TCP/UDP-Proxy. Kein Cert. Für SSH, Minecraft, DNS, MQTT-plain.tls_mode=passthrough— Caddy-L4 schaut nur auf SNI, reicht die TLS-Session 1:1 ans Backend weiter. Cert muss im Backend liegen.tls_mode=terminate— Caddy-L4 terminiert TLS mit einem Let's-Encrypt-Cert auf Server-Seite, spricht dann plain TCP ins Backend.
Default-Modus ist none, weil L4 meistens für Protokolle genutzt wird
die kein TLS haben.
TLS-Gebühr: Port 80 ist Pflicht
ACME-HTTP-01 braucht einen erreichbaren Port 80. Der Cert-Refresh (alle ~60 Tage automatisch) ebenso. Wenn Port 80 blockiert ist oder hinter einer Drittfirewall liegt: Cert kommt nicht und läuft ab.
Die Zertifikate-Seite lesen
/certificates listet für jede Route mit Domain den aktuellen
Cert-Status. Die Status-Tags:
| Tag | Bedeutung | Wann? |
|---|---|---|
| Auto-TLS (grün) | Let's-Encrypt-Cert wird aktiv managed | Normaler Fall, HTTP-Route mit Domain |
| HTTP ohne TLS (amber) | Legacy-Status, sollte nach v1.47.3 nicht mehr vorkommen | Historische Routen mit https_enabled=0 — werden inzwischen als Auto-TLS behandelt |
| TLS-Passthrough (grau) | L4-Route mit tls_mode=passthrough |
Cert liegt am Backend, Server terminiert nicht |
| L4 ohne TLS (grau) | L4-Route mit tls_mode=none |
Purer TCP/UDP-Proxy |
| Caddy offline (rot) | Caddy-Prozess läuft nicht | Supervisord hat Caddy verloren → Admin-Aktion nötig |
Die Tags werden aus dem Zusammenspiel von Route-Typ,
https_enabled/l4_tls_mode und Caddy-Runtime-Status gerechnet. Der
Code lebt in src/services/caddyConfig.js + dem Routes-Service.
Troubleshooting
"Mein Cert wird nicht issued"
Der weitaus häufigste Fall. Checkliste:
-
DNS prüfen. Der A-Record muss auf die öffentliche IPv4 des GateControl-Servers zeigen.
dig +short cloud.example.com # muss deine Server-IP seinOder im Admin-UI: im Route-Wizard steht ein DNS-Check-Button,
POST /api/v1/routes/check-dns. -
Port 80 von außen erreichbar. Test von einer externen Maschine:
curl -I http://cloud.example.com/muss mindestens TCP-Connect bringen. Wenn
Connection refusedoder Timeout: Router-Firewall, Server-Firewall oder belegt durch einen anderen Prozess. -
Port 443 von außen erreichbar. Gleicher Test mit
https://. Bei "unable to verify the first certificate" ist das OK — der Connect steht, nur Cert ist noch nicht valide. -
Cloudflare-Proxy aus. Wenn CF als Proxy davor sitzt (orange Wolke), scheitert HTTP-01: LE sieht die CF-IP statt unserer IP und bekommt beim Challenge-Abruf die falsche Antwort.
-
Let's-Encrypt-Rate-Limit. 50 Certs pro registered Domain pro Woche, 5 Duplicate-Certs pro Woche, 5 Fehlversuche pro Stunde. Wenn du viel rumspielst, gehst du da rein. Remedy: auf LE-Staging umstellen während Tests:
# im Server-.env GC_CADDY_ACME_CA=https://acme-staging-v02.api.letsencrypt.org/directoryStaging-Certs sind nicht browser-trusted, aber zum Prüfen des Flows reicht's. Danach auf Production zurückstellen.
-
Caddy-Logs einsehen:
docker logs gatecontrol 2>&1 | grep -i "acme\|tls.obtain\|certificate"Typische Fehlermeldungen:
authorization failed: Invalid response from …→ Port 80 nicht erreichbar, oder Challenge-Pfad nicht routbar.too many registrations for this IP→ LE-Limit.timeout waiting for response from DNS→ DNS langsam, oft nach Propagation weg.
"Cert wurde issued, aber der Browser warnt trotzdem"
-
Cert für falsche Domain? Caddy holt den Cert auf die Domain die im Host-Matcher steht. Wenn du
www.example.comaufrufst aber die Routeexample.comheißt, holt Caddy das Cert nur fürexample.com. Lösung: entweder zweite Route fürwww., oder im Route-Wizard mehrere Domains hinterlegen. -
Intermediate-Chain unvollständig?
openssl s_client -servername cloud.example.com -connect cloud.example.com:443 -showcerts < /dev/nullSuche nach "verify return code: 0 (ok)". Bei LE-Certs sollte der E5/E6-Intermediate mit ausgeliefert werden.
-
Falsches Cert via SNI: Caddy-Admin-API befragen:
curl -s http://127.0.0.1:2019/pki/ca/local | jq .oder über den internen Admin-Endpoint, um zu sehen was Caddy für welche Domain cached.
"HTTPS funktioniert obwohl ich https_enabled abgeschaltet habe"
Erwartetes Verhalten seit dem srv0-Merge-Refactor. Das Flag ist
derzeit wirkungslos. Siehe Abschnitt "Alle HTTP-Routen teilen sich
einen Server-Block" oben.
Wenn du HTTPS für eine spezifische Route wirklich aus willst (warum
auch immer — es gibt selten einen guten Grund): Route als L4 mit
tls_mode=none anlegen und einen HTTP-Custom-Listen-Port wählen. Oder
den Caddy-Template-Code patchen.
"L4-Route mit TLS-terminate kriegt keinen Cert"
Caddy-L4 handhabt TLS anders als Caddy-HTTP. Ohne expliziten SNI-Matcher in der L4-Config weiß Caddy nicht für welche Domain das Cert gilt.
Workaround: In der L4-Route die Domain setzen und TLS-Policies manuell
pflegen. Die GateControl-Admin-UI exponiert dazu aktuell keinen
komfortablen Schalter — wenn du wirklich L4-terminate brauchst, ist der
empfohlene Weg eine separate HTTP-Route auf einen custom Port. Für die
Mehrheit der L4-Use-Cases ist none oder passthrough eh richtiger.
"Caddy offline" — roter Tag
Caddy-Prozess läuft nicht. Der supervisord-Watchdog sollte ihn automatisch neu starten, tut es aber gelegentlich nicht (z.B. nach OOM-Kill oder bei kaputter Config-Reload-Cycle).
Fix:
docker exec gatecontrol supervisorctl status caddy
docker exec gatecontrol supervisorctl restart caddy
Danach im Admin-UI Caddy reload klicken (Settings → Caddy). Der Server wirft seine in-memory Config neu ins Caddy-Admin-API.
"Cert ist abgelaufen und erneuert sich nicht"
Caddy erneuert ~30 Tage vor Ablauf automatisch. Wenn das nicht klappt:
- Port 80 muss erreichbar sein (auch für Renewals).
- Caddy-Daten-Volume persistent? Wenn
/datanicht gemountet ist, sind bei jedem Container-Restart sämtliche Certs weg. Check:docker inspect gatecontrol | grep -A 5 Mounts - Manueller Renewal-Kick:
(Bzw. im GateControl-Fall: Admin-UI → Caddy reload.)docker exec gatecontrol caddy reload --config /etc/caddy/Caddyfile
Weiterführend
- ../FORCE-HTTPS.md — HTTP-→-HTTPS-Redirect-Feinheiten.
- ../BACKEND-HTTPS.md — HTTPS zum Backend (separat vom Cert-am-Frontend).
- ../concepts/routing.md — Deep-Dive HTTP-Routing-Pipeline (Host-Matcher, Subroute, L4-Layer).
- first-gateway-setup.md — Gateway-Routen
sind bzgl. TLS simpler, weil sie alle durch
srv0mit Auto-TLS laufen. - adding-a-route.md — Wizard-Walkthrough inkl. DNS-Check-Schritt.