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:

  1. Go to Cloudflare Dashboard → My Profile → API Tokens
  2. Click Create Token → select the Edit zone DNS template
  3. Set permissions:
    • Zone → DNS → Edit
    • Zone → Zone → Read
  4. Select your domain under Zone Resources (e.g., example.com)
  5. 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

References