CallMeTechie
EN Anmelden
Home Produkte Blog Über mich Kontakt

TLS / Zertifikate verstehen und debuggen

🌐 Netzwerk & Routing · Updated vor 1 Monat

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 das acme_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:

  1. DNS prüfen. Der A-Record muss auf die öffentliche IPv4 des GateControl-Servers zeigen.

    dig +short cloud.example.com
    # muss deine Server-IP sein
    

    Oder im Admin-UI: im Route-Wizard steht ein DNS-Check-Button, POST /api/v1/routes/check-dns.

  2. Port 80 von außen erreichbar. Test von einer externen Maschine:

    curl -I http://cloud.example.com/
    

    muss mindestens TCP-Connect bringen. Wenn Connection refused oder Timeout: Router-Firewall, Server-Firewall oder belegt durch einen anderen Prozess.

  3. 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.

  4. 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.

  5. 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/directory
    

    Staging-Certs sind nicht browser-trusted, aber zum Prüfen des Flows reicht's. Danach auf Production zurückstellen.

  6. 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"

  1. Cert für falsche Domain? Caddy holt den Cert auf die Domain die im Host-Matcher steht. Wenn du www.example.com aufrufst aber die Route example.com heißt, holt Caddy das Cert nur für example.com. Lösung: entweder zweite Route für www., oder im Route-Wizard mehrere Domains hinterlegen.

  2. Intermediate-Chain unvollständig?

    openssl s_client -servername cloud.example.com -connect cloud.example.com:443 -showcerts < /dev/null
    

    Suche nach "verify return code: 0 (ok)". Bei LE-Certs sollte der E5/E6-Intermediate mit ausgeliefert werden.

  3. 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:

  1. Port 80 muss erreichbar sein (auch für Renewals).
  2. Caddy-Daten-Volume persistent? Wenn /data nicht gemountet ist, sind bei jedem Container-Restart sämtliche Certs weg. Check:
    docker inspect gatecontrol | grep -A 5 Mounts
    
  3. Manueller Renewal-Kick:
    docker exec gatecontrol caddy reload --config /etc/caddy/Caddyfile
    
    (Bzw. im GateControl-Fall: Admin-UI → Caddy reload.)

Weiterführend

Cookie Settings

Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Essentielle Cookies sind immer aktiv.

Datenschutzerklärung
ESC
↑↓ navigate open esc close