Zum Inhalt

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/2 und HTTP/3 QUIC) aktiviert, eine integrierte Smallstep PKI (tls internal) für vertrauenswürdige interne LAN-Zertifikate bereitstellt, maßgeschneiderte DNS-01-Module via xcaddy kompiliert und eine transaktionale JSON-API auf localhost:2019 anbietet.

  • 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:

/opt/caddy/Dockerfile
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
/opt/caddy/compose.yaml
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

/opt/caddy/Caddyfile
{
    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:

Caddy Root-CA unter Linux importieren
# 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

Caddyfile formatieren, validieren und neu laden
# 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