Wires Caddy as the ACME client side of our new self-hosted DNS-01 flow. Proves the design end-to-end: caddy-dns/rfc2136 -> our CoreDNS rfc2136 plugin -> zone file write -> git auto-commit -> HE AXFR -> LE validates -> cert issued. Changes: - caddy/Dockerfile: --with github.com/caddy-dns/rfc2136 added alongside the existing caddy-dns/vultr. - caddy/Caddyfile: new test-rfc2136.supported.systems site that uses the new provider. server coredns:53 (docker internal), key from env, propagation_delay 60s + timeout 600s to accommodate HE pull. - docker-compose.yml: ACME_TSIG_SECRET passed to the caddy container (the same secret CoreDNS verifies on the other side of the loop). First cert issued in production: 2026-05-21 ~13:23 UTC. ~5.5 min end-to-end from Caddy starting to cert in hand. Documented in session notes; the cert sits unused in caddy-data/ until/unless something publishes ports 80/443 for that hostname.
71 lines
3.0 KiB
Caddyfile
71 lines
3.0 KiB
Caddyfile
# Caddy is used here purely as an ACME client + cert renewer for CoreDNS.
|
|
# The HTTPS site is technically served (Caddy can't issue without a site
|
|
# block), but we don't expose port 443 from this container — only the
|
|
# cert files in /data/caddy/ are consumed by the CoreDNS sidecar.
|
|
{
|
|
# Operator contact for Let's Encrypt; also used for expiry warnings.
|
|
email {$ACME_EMAIL}
|
|
|
|
# Skip the HTTP-to-HTTPS redirect server (we have nothing to redirect).
|
|
# Caddy still binds :443 inside the container for the cert site, which
|
|
# is fine because we don't publish those ports to the host.
|
|
auto_https disable_redirects
|
|
}
|
|
|
|
{$CADDY_HOSTNAME} {
|
|
tls {
|
|
# DNS-01 challenge via Vultr API. The plugin reads the token from
|
|
# the named env var; setting via {env.VULTR_API_KEY} would also
|
|
# work but the bare reference is clearer with Caddy's modules.
|
|
dns vultr {env.VULTR_API_KEY}
|
|
|
|
# Use PUBLIC resolvers for the propagation check, not Docker's
|
|
# embedded DNS. Without this, Caddy follows the container's
|
|
# resolv.conf → host's resolv.conf → local LAN resolvers, which
|
|
# on a split-horizon DNS setup will return LAN IPs for vultr.com
|
|
# nameservers and the propagation check fails with connection
|
|
# refused. Hitting 1.1.1.1 / 9.9.9.9 directly sidesteps it.
|
|
resolvers 1.1.1.1 9.9.9.9 1.0.0.1
|
|
|
|
# Vultr's NS propagation is generally fast (<30s) but LE checks
|
|
# multiple resolvers; cushion the wait to avoid flaky issuance.
|
|
propagation_delay 30s
|
|
propagation_timeout 300s
|
|
}
|
|
|
|
# A sensible response if anyone hits this on 443. Doubles as a
|
|
# "Caddy is alive" sanity check inside the compose network.
|
|
respond "CoreDNS DoT/DoH endpoint. DoT: port 853. DoH: /dns-query" 200
|
|
}
|
|
|
|
# ─── Test domain using caddy-dns/rfc2136 → our own CoreDNS plugin ──
|
|
#
|
|
# Proves the full self-hosted ACME DNS-01 loop end-to-end:
|
|
# 1. Caddy attempts to issue a cert for test-rfc2136.supported.systems
|
|
# 2. Caddy uses caddy-dns/rfc2136 to UPDATE _acme-challenge.<name>
|
|
# TXT record into supported.systems via our CoreDNS plugin
|
|
# 3. Plugin verifies TSIG, writes the zone file, bumps SOA
|
|
# 4. CoreDNS reloads (auto plugin, ~30s)
|
|
# 5. HE pulls the new serial within ~300s
|
|
# 6. Let's Encrypt validates from public DNS via HE
|
|
# 7. Caddy issues the cert
|
|
test-rfc2136.supported.systems {
|
|
tls {
|
|
dns rfc2136 {
|
|
key_name acme-update-key.
|
|
key_alg hmac-sha256
|
|
key {env.ACME_TSIG_SECRET}
|
|
# `coredns` resolves on the docker network to the CoreDNS
|
|
# container; port 53 is its in-container listener.
|
|
server coredns:53
|
|
}
|
|
# Use public resolvers (not docker's embedded DNS) for the
|
|
# post-update propagation check. Allow HE plenty of time to pull.
|
|
resolvers 1.1.1.1 9.9.9.9 1.0.0.1
|
|
propagation_delay 60s
|
|
propagation_timeout 600s
|
|
}
|
|
respond "test-rfc2136: cert issued via self-hosted RFC 2136 ACME flow!" 200
|
|
}
|