Caddy Cloudflare DNS-01: Get Wildcard TLS Certificates
Why Use DNS-01 Instead of HTTP-01
Let’s Encrypt offers two main challenge types:
| Type | Mechanism | Use Case | Limitation |
|---|---|---|---|
| HTTP-01 | Serve a specific file on port 80 | Single domain, publicly reachable | Cannot issue wildcard certificates |
| DNS-01 | Write a challenge token in DNS TXT record | Wildcard certificates, internal services | Requires DNS provider API |
If your service runs on an internal network with only some ports exposed, or you need a wildcard certificate like *.example.com, you need DNS-01.
Caddy uses HTTP-01 by default. To switch to DNS-01, install the caddy-dns/cloudflare plugin (see the xcaddy build article), then specify acme_dns in the global options.
Configure DNS-01
{
acme_dns cloudflare <API_TOKEN>
}That’s it. One line. At startup, Caddy calls the Cloudflare API to create a _acme-challenge.example.com TXT record. After Let’s Encrypt verifies it, Caddy deletes the record automatically. No manual work.
Certificate renewal is automatic too. Caddy checks expiration in the background and runs the same flow again before expiry.
Cloudflare API Token Permissions
Don’t use the Account API Key (global key) here. Create a limited-scope API Token instead:
- Go to Cloudflare Dashboard → My Profile → API Tokens
- Click Create Token → select the Edit zone DNS template
- Set permissions:
- Zone → DNS → Edit
- Zone → Zone → Read
- Select your domain under Zone Resources (e.g.,
example.com) - Set TTL to never or as needed
Even if the token leaks, the attacker can only modify DNS records for your domain. Nothing else.
Global Caddyfile Configuration
{
acme_dns cloudflare xxx
servers {
trusted_proxies cloudflare {
interval 12h
timeout 15s
}
}
}Cloudflare Trusted Proxies
trusted_proxies cloudflare {
interval 12h
timeout 15s
}When Caddy sits behind Cloudflare CDN, Cloudflare’s proxy IPs replace the real source IP of each request. Caddy needs to know which IPs are trusted so it can correctly extract the real client IP from the X-Forwarded-For header.
The caddy-cloudflare-ip plugin (already included in the custom build) automatically fetches Cloudflare’s IP range list and refreshes it every 12 hours. If Cloudflare adds new origin IPs, the config picks them up automatically. No manual update needed.
timeout 15s specifies the timeout for fetching the IP ranges.
Verify the Certificate
After deployment, verify with OpenSSL:
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -text | grep -A1 "Subject Alternative Name"You should see output like:
X509v3 Subject Alternative Name:
DNS:*.example.com, DNS:example.com