Automatisiertes TLS-Ingress: Caddy v2 Go-Architektur, On-Demand ACME, Interne PKI & xcaddy
Erstellt: 06.11.2026 Aktualisiert: 06.11.2026
Management Summary: Traditionelle Webserver erfordern oft externe ACME-Cronjobs, manuelle OCSP-Stapling-Konfigurationen und fehleranfällige Reload-Skripte. Dieser Leitfaden beschreibt das Produktiv-Deployment von Caddy v2 – einem speichersicheren Go-Webserver und Reverse-Proxy, der Automatisches HTTPS standardmäßig (
HTTP/1.1,HTTP/2undHTTP/3 QUIC) aktiviert, eine integrierte Smallstep PKI (tls internal) für vertrauenswürdige interne LAN-Zertifikate bereitstellt, maßgeschneiderte DNS-01-Module viaxcaddykompiliert und eine transaktionale JSON-API auflocalhost:2019anbietet.
-
Automatisches HTTPS ohne Konfigurationsaufwand
Verwaltet Let's Encrypt und ZeroSSL Multi-CA-Fallbacks, OCSP-Stapling, TLS-1.3-Aushandlung und HTTP/3 (
QUIC) vollautomatisch. -
Integrierte interne PKI (
tls internal)
Betreibt eine vollwertige Root- und Intermediate-CA auf Basis von Smallstep für kurzlebige Zertifikate interner
.lan-Zonen. -
Maßgeschneiderte
xcaddy-Builds
Kompiliert statische Go-Binaries mit Cloudflare-/RFC2136-DNS-01-Providern oder Coraza-WAF-Modulen in einem sauberen Container-Build-Step.
-
Caddyfile-Adapter & JSON-API
Ermöglicht lesbare
Caddyfile-Snippets, die deterministisch in Caddys atomaren JSON-Laufzeitzustand übersetzt werden.
1. Architektur: Caddyfile-Adapter, CertMagic & Interne PKI
Unter der Haube ist Caddys natives Konfigurationsformat ein streng typisiertes JSON-Dokument. Der Caddyfile-Adapter übersetzt lesbare Caddyfiles atomar in JSON und lädt sie ohne Socket-Unterbrechung. Die Zertifikatsverwaltung übernimmt CertMagic, das verteilte Speicher-Locks und automatische Fallbacks steuert.
%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
subgraph ConfigPlane["Konfiguration & Build-Pipeline"]
Caddyfile["Deklaratives Caddyfile<br/>Snippets + Matchers"]
Adapter["JSON Config Adapter<br/>Atomarer Hot-Reload (:2019)"]
end
subgraph CaddyRuntime["Caddy v2 Engine (Go + CertMagic)"]
Listeners["HTTP/1.1, HTTP/2 & HTTP/3<br/>TCP/443 + UDP/443"]
CertMagic["CertMagic ACME Engine<br/>Let's Encrypt + ZeroSSL"]
StepPKI["Smallstep Interne CA<br/>tls internal (.lan Zonen)"]
end
subgraph Upstreams["Upstream-Service-Pool"]
ActiveHC["Active / Passive Health-Checks<br/>lb_policy least_conn"]
Backends["Container Backends<br/>FastCGI / Reverse Proxy"]
end
Caddyfile -->|"caddy adapt / reload"| Adapter
Adapter --> Listeners
Listeners <--> CertMagic
Listeners <--> StepPKI
Listeners --> ActiveHC
ActiveHC --> Backends
2. Eigenes Caddy-Binary mit xcaddy bauen (DNS-01 Wildcards)
Für Wildcard-Zertifikate (*.nejat-hakan.de) ohne öffentliche Freigabe von Port 80/443 wird Caddy mit dem entsprechenden DNS-Provider-Plugin gebaut:
FROM caddy:2.8-builder-alpine AS builder
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare \
--with github.com/caddy-dns/rfc2136 \
--with github.com/WeidiDeng/caddy-cloudflare-ip
FROM caddy:2.8-alpine
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
services:
caddy:
build: .
container_name: caddy-ingress
restart: unless-stopped
ports:
- "80:80/tcp"
- "443:443/tcp"
- "443:443/udp"
environment:
TZ: "Europe/Berlin"
CF_API_TOKEN: "${CF_API_TOKEN}"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data:/data:Z
- ./config:/config:Z
cap_add:
- NET_ADMIN
Verzeichnis /data zwingend persistent sichern
Caddy speichert Kontoschlüssel, X.509-Zertifikate, OCSP-Staples und die interne Root-CA unter /data/caddy. Geht dieses Volume verloren, greifen bei Neustarts schnell die Let's Encrypt Rate-Limits (5 Duplikate pro Woche).
3. Produktives Caddyfile: Snippets, Wildcards & ForwardAuth
{
email hostmaster@nejat-hakan.de
admin localhost:2019
servers {
protocols h1 h2 h3
trusted_proxies static private_ranges
}
}
# --- Wiederverwendbares Sicherheits-Header-Snippet ---
(security_headers) {
header {
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "accelerometer=(), camera=(), geolocation=(), microphone=(), usb=()"
-Server
}
encode zstd gzip
}
# --- Wiederverwendbares SSO ForwardAuth Gate (Authelia / Authentik) ---
(sso_gate) {
forward_auth authelia:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Emails Remote-Name
}
}
# --- 1. Öffentliche Wildcard-Domain ---
*.nejat-hakan.de {
tls {
dns cloudflare {env.CF_API_TOKEN}
resolvers 1.1.1.1 9.9.9.9
}
import security_headers
@vault host vault.nejat-hakan.de
handle @vault {
reverse_proxy vaultwarden:80 {
header_up X-Real-IP {remote_host}
}
}
@git host git.nejat-hakan.de
handle @git {
request_body {
max_size 2GB
}
reverse_proxy forgejo:3000
}
@grafana host metrics.nejat-hakan.de
handle @grafana {
import sso_gate
reverse_proxy grafana:3000 {
lb_policy least_conn
health_uri /api/health
health_interval 15s
health_timeout 5s
}
}
# Standard-Fallback für nicht konfigurierte Subdomains
handle {
abort
}
}
# --- 2. Interne LAN-Dienste via Smallstep Internal PKI ---
proxmox.lan.nejat-hakan.de {
tls internal
import security_headers
reverse_proxy https://192.168.10.20:8006 {
transport http {
tls_insecure_skip_verify
}
}
}
4. Caddys interne Root-CA auf Workstations hinterlegen
Bei Nutzung von tls internal erzeugt Caddy eine langlebige Root-CA unter /data/caddy/pki/authorities/local/root.crt. Diese kann unkompliziert in den System-Truststore importiert werden:
# 1. Root-Zertifikat aus dem Container kopieren
sudo cp /opt/caddy/data/caddy/pki/authorities/local/root.crt \
/usr/local/share/ca-certificates/caddy-internal-root-ca.crt
# 2. System-Zertifikatsbundle aktualisieren
sudo update-ca-certificates
# 3. Optional: In NSS-Datenbank für Browser einpflegen
certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "Caddy Internal CA" \
-i /usr/local/share/ca-certificates/caddy-internal-root-ca.crt
5. Verifikation & Unterbrechungsfreie Hot-Reloads
# 1. Einrückungen im Caddyfile automatisch angleichen
docker exec caddy-ingress caddy fmt --overwrite /etc/caddy/Caddyfile
# 2. Syntax prüfen
docker exec caddy-ingress caddy validate --config /etc/caddy/Caddyfile
# 3. Unterbrechungsfreien Reload via localhost:2019 ausführen
docker exec caddy-ingress caddy reload --config /etc/caddy/Caddyfile