Skip to content

Agent Handoff: Caddy, Technitium, and HTTPS

Updated: 2026-06-22

Use this file as the short operational context for future agents. It is intentionally small and does not contain secrets.

Live Topology

Role Target Address Notes
Firewall/router OPNsense VM 100 192.168.2.1 on DMZ DHCP, routing, firewall policy
Apps CT 101 podman-lxc 192.168.2.20 Rootless Podman app host
DNS CT 102 technitium-dns 192.168.2.2 Internal DNS resolver
Ingress CT 103 caddy-ingress 192.168.2.3 Caddy HTTP/HTTPS ingress

Known app upstreams on CT 101:

App Upstream
Forgejo http://192.168.2.20:30080
Vaultwarden http://192.168.2.20:30081
Adminer/dbgui http://192.168.2.20:30082
Dozzle/monitor http://192.168.2.20:30083
Technitium console http://192.168.2.2:5380
Synology office route https://192.168.0.50:5001

Current internal DNS records in Technitium:

Hostname Address
git.kh3group.com 192.168.2.3
pass.kh3group.com 192.168.2.3
dbgui.kh3group.com 192.168.2.3

Current expected HTTPS behavior:

Hostname Expected result
https://git.kh3group.com 200, proxies to Forgejo
https://pass.kh3group.com 200, proxies to Vaultwarden
https://dbgui.kh3group.com 403, intentionally blocked until OIDC/admin policy exists
https://office.kh3group.com 200, proxies to Synology HTTPS with an upstream self-signed-certificate exception
https://monitor.kh3group.com 200, proxies to Dozzle; must be restricted before broad use
https://dns.kh3group.com 200, proxies to Technitium web console; must be restricted before broad use

Important Paths

On CT 103 caddy-ingress:

Purpose Path
Caddy binary /usr/local/bin/caddy
Caddyfile /opt/caddy/config/Caddyfile
Caddy env file /opt/caddy/env/caddy.env
Caddy data and certificates /opt/caddy/data
systemd unit /etc/systemd/system/caddy.service

On CT 102 technitium-dns:

Purpose Path
Technitium backup directory /root/technitium-backups
Web console listener 127.0.0.1:5380 and 192.168.2.2:5380

Do not print or commit raw env files, Caddy data, Technitium config/backups, OPNsense exports, tokens, private keys, or ACME account material.

Current Caddy Design

Caddy was custom-built as v2.11.4 with these required modules:

  • dns.providers.cloudflare
  • security

Caddy admin is disabled. Because of that, caddy reload and systemctl reload caddy are not usable. Validate the Caddyfile, then restart:

ssh pve 'pct exec 103 -- sh -c "set -a; . /opt/caddy/env/caddy.env; set +a; /usr/local/bin/caddy validate --config /opt/caddy/config/Caddyfile"'
ssh pve 'pct exec 103 -- systemctl restart caddy'

The systemd service must not use --environ, because that prints environment variables, including tokens, into the journal.

Good service start shape:

ExecStart=/usr/local/bin/caddy run --config /opt/caddy/config/Caddyfile

Remove any ExecReload= line unless Caddy admin is intentionally re-enabled.

Caddyfile Pattern

The working pattern uses explicit public ACME DNS resolvers. This matters because CT 103 uses internal DNS, and the Cloudflare provider can fail zone discovery with expected 1 zone, got 0 if it follows internal DNS for kh3group.com.

{
    admin off
    email [email protected]
    storage file_system /opt/caddy/data
    acme_ca https://acme-v02.api.letsencrypt.org/directory
}

(tls_cloudflare) {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        resolvers 1.1.1.1 1.0.0.1
    }
}

git.kh3group.com {
    import tls_cloudflare
    encode zstd gzip
    reverse_proxy 192.168.2.20:30080
}

pass.kh3group.com {
    import tls_cloudflare
    encode zstd gzip
    reverse_proxy 192.168.2.20:30081
}

dbgui.kh3group.com {
    import tls_cloudflare
    respond "dbgui disabled until OIDC/admin policy is configured" 403
}

/opt/caddy/env/caddy.env should include:

CADDY_HOME=/opt/caddy
CLOUDFLARE_API_TOKEN=REDACTED

The Cloudflare token must be a scoped API token, not the old global API key. Required permissions:

Zone / Zone / Read
Zone / DNS / Edit
Zone Resources: Include / Specific zone / kh3group.com

Validation Commands

Do not print secrets while validating.

Check modules:

ssh pve 'pct exec 103 -- /usr/local/bin/caddy list-modules --versions | grep -E "dns.providers.cloudflare|security"'

Validate Caddyfile with env loaded:

ssh pve 'pct exec 103 -- sh -c "set -a; . /opt/caddy/env/caddy.env; set +a; /usr/local/bin/caddy validate --config /opt/caddy/config/Caddyfile"'

Restart and check listeners:

ssh pve 'pct exec 103 -- systemctl restart caddy; pct exec 103 -- ss -ltnp | grep -E ":80|:443"'

Validate current routes from CT 103:

ssh pve 'pct exec 103 -- sh -c '"'"'for h in git.kh3group.com pass.kh3group.com dbgui.kh3group.com office.kh3group.com monitor.kh3group.com dns.kh3group.com; do printf "%s " "$h"; curl -sS -o /dev/null -w "%{http_code} verify=%{ssl_verify_result}\n" --resolve "$h:443:127.0.0.1" "https://$h/"; done'"'"''

Expected:

git.kh3group.com 200 verify=0
pass.kh3group.com 200 verify=0
dbgui.kh3group.com 403 verify=0
office.kh3group.com 200 verify=0
monitor.kh3group.com 200 verify=0
dns.kh3group.com 200 verify=0

Validate internal DNS:

ssh pve 'pct exec 102 -- dig +short @192.168.2.2 git.kh3group.com A'

Validate from a Mac client:

dig git.kh3group.com +short
curl -Iv https://git.kh3group.com
curl -Iv https://pass.kh3group.com
curl -Iv https://dbgui.kh3group.com

Troubleshooting Lessons

  • If manual caddy validate says missing API token, load the env file in the shell before validating.
  • If systemd starts but manual validation fails, remember systemd reads /opt/caddy/env/caddy.env; your shell does not.
  • If logs say contact email has forbidden domain "example.com", replace the placeholder email in the Caddyfile with a real address.
  • If logs say Invalid access token, the Cloudflare token is wrong, revoked, or lacks required permissions.
  • If direct Cloudflare token verification succeeds but Caddy says expected 1 zone, got 0, add public resolvers in the tls block.
  • If certificates are issued but a route returns 502, check the upstream port in the Caddyfile and validate direct connectivity from CT 103.
  • Caddy admin is disabled by design. Do not add public admin exposure.
  • Adminer/dbgui must stay blocked until OIDC/admin policy is configured and validated.
  • Dozzle and Technitium console routes also require OIDC, VPN, or source-IP restrictions before they are treated as safe for normal operations.