Tutorial

DNS-01 without handing every host your DNS API keys: CNAME delegation done right

June 30, 20269 min readCertPulse Engineering

Why DNS-01 keeps winning

If you've automated certificate issuance at any scale, you already know the three ACME challenge types aren't interchangeable. HTTP-01 wants port 80 reachable from the public internet. TLS-ALPN-01 wants port 443 and a listener that speaks the ALPN handshake. Both fall apart the moment you need a wildcard, because there's no single hostname for the CA to probe. A wildcard covers names that don't exist yet.

DNS-01 does everything the other two can't. It's the only challenge that issues wildcards. It works for origins with no inbound 80 or 443: internal load balancers, databases behind a firewall, the mTLS mesh that never touches the public internet. The CA never connects to your host. It asks an authoritative resolver for a TXT record, and that's it. If you can write a DNS record, you can prove control.

That's always been useful. Under the lifetimes coming down the pipe, it's becoming the default. CA/Browser Forum ballot SC-081v3 walks maximum certificate lifetimes from 398 days down to 47 by March 2029, and the schedule is already moving. 200 days is in effect as of March 2026. 100 days lands March 2027. Shorter lifetimes mean you renew far more often. A cert you used to touch once a year you'll now touch eight or nine times a year, per name, forever. Whatever challenge you pick, you're going to run it a lot more, and DNS-01's flexibility is exactly what you want when renewal stops being an event and turns into a heartbeat.

There's a catch nobody prices in until it bites them.

The token that can rewrite your whole zone

The naive DNS-01 setup works like this. Your renewing host (certbot, lego, cert-manager, whatever) needs to create a TXT record at _acme-challenge.example.com. So you hand it an API token for your DNS provider: Route 53, Cloudflare, NS1, Google Cloud DNS. The token can write records in the zone.

Read that again. It can write records, not "the one TXT record ACME needs." Most provider APIs scope credentials to a zone, not to a record name or type. A token that can create the _acme-challenge.example.com TXT can also, in the same breath, rewrite your MX records, flip your SPF, repoint your login page's A record at an attacker's box, or drop a TXT that satisfies someone else's domain-validation challenge for a cert on your name.

Now think about where that token lives. It sits in a secret on every host that renews a certificate. Every node in the cert-manager deployment. Every VM running a certbot cron. The CI runner that bakes images. In a fleet, that's dozens or hundreds of copies of a credential that controls your entire DNS zone, which functionally controls your email, your domain validation, and the trust anchor for every service you run.

One compromised renewer and the attacker doesn't get one cert. They get your zone. They can issue certificates for any name in it from any CA that does DNS validation, redirect your mail, and pass SPF and DKIM checks while they do it. "An attacker popped a web server" just became "an attacker owns the namespace."

The fix isn't to abandon DNS-01. It's to make the credential on the host able to write exactly one thing and nothing else.

Delegation: point the challenge somewhere disposable

The mechanism is a CNAME. _acme-challenge.example.com doesn't have to be a TXT record living in your real zone. It can be a CNAME pointing at a record in some other zone. When the CA looks up _acme-challenge.example.com, DNS follows the CNAME and the CA reads the TXT value from wherever it lands. The CA doesn't care where the answer physically lives, only that the chain resolves to the right token.

So you create the CNAME once, by hand, as a permanent piece of zone configuration. It never changes again. Then you put the target zone, the place the CNAME points, somewhere with its own credentials, scoped so tightly that the worst an attacker can do with a stolen renewer credential is overwrite one validation token. They can satisfy a challenge for a name you already control. They can't touch your MX, your SPF, or anything else.

There are two real patterns for the target zone. Pick based on whether you'd rather run a small piece of software or manage a second delegated zone at a provider.

Pattern one: acme-dns

acme-dns is a purpose-built DNS server that does one thing: answer ACME challenges. You run it (a single Go binary, SQLite or Postgres behind it), delegate a subdomain to it with NS records, and each host that needs to validate registers itself once. Registration hands back a random username, password, and a long random subdomain under the acme-dns zone. That credential can update one TXT record, the one for that subdomain, and nothing else. Not other hosts' records. Not other zones. Nothing.

This is the cleanest blast-radius story available. You point _acme-challenge.example.com at the random subdomain acme-dns gave you, set it once, and forget it. The credential on the host is worthless for anything except answering that host's own challenge. If it leaks, the attacker can pass a validation for a name they'd already need to be attacking through some other vector. There's no lateral path to the rest of your DNS.

The cost: you're running a DNS server, and it has to be authoritative and publicly reachable for the delegated zone. It's a small, boring service, but it's still a service. It needs to be up when you renew, it needs monitoring, it's another line in the runbook. For a lot of teams that tradeoff is obviously worth it, and acme-dns is supported directly by certbot, lego, cert-manager, and most of the ACME clients you'd actually reach for.

Pattern two: a delegated TXT-only zone at a provider

If you don't want to run anything, delegate a subdomain to a separate zone (same provider in a different account, or a second provider entirely) and scope a credential to that zone, TXT records only where the API allows it. _acme-challenge.example.com CNAMEs into example.acme.example-delegated.net or similar, and the only token on your hosts writes into the delegated zone.

This is less airtight than acme-dns, because most provider tokens still can't scope below the zone level. A stolen credential can rewrite any TXT in the delegated zone, which might cover multiple names. But that zone holds nothing but challenge records. No MX, no SPF, no A record pointing at anything that matters. The damage ceiling is "an attacker can mess with certificate validation for the names in this throwaway zone," which is a galaxy away from "an attacker owns example.com." You've traded a tiny operational cost for a credential that can't touch anything you care about.

The gotchas that will actually trip you

CAA still lives on the real zone. Delegating _acme-challenge does not delegate your CAA records. CAA is read at the issuing name and walked up the real tree (example.com, then the parent), not down the CNAME. If you restrict issuance with CAA, and you should, those records stay in your apex zone and you maintain them there. Don't assume delegation moved them. It didn't.

Split-horizon resolvers will lie to the CA's face. If you run internal DNS that answers differently than the public internet, a common setup for internal services, make sure the _acme-challenge CNAME and its target resolve publicly. The CA validates from the outside. I once watched a renewal fail for an hour because the challenge record was perfect on the internal resolver and didn't exist on the authoritative public nameservers. The CA only sees what the public internet sees.

Public resolvers won't follow a broken chain. CNAME chains have to resolve end to end on public infrastructure. A target zone that isn't properly delegated with NS records, a CNAME pointing at a name that doesn't exist, a dangling link left over from a half-finished migration: the CA gets SERVFAIL or NXDOMAIN and validation fails. Test the full chain with a public resolver before you trust it. Not your laptop's resolver, which might be caching something kind.

Operating this in the 47-day world

Two things change when you validate constantly instead of once a year.

First, propagation timing stops being a rounding error. Every renewal writes a TXT, waits for it to be visible, and asks the CA to check. Keep TTLs on the challenge records low, single-digit minutes or less, so stale answers don't linger across the many renewals you're now running. acme-dns serves fresh values immediately because that's the entire job. A general provider with a longer minimum TTL can leave you waiting.

Second, multi-perspective issuance corroboration makes flaky authoritative DNS hurt more than it used to. CAs now validate from several network vantage points and require agreement before issuing. Good for security, since it's much harder to spoof a challenge when the CA checks from multiple continents. But a sometimes-up authoritative nameserver, or a delegated zone that resolves from some networks and not others, turns intermittent failures into hard refusals. Your delegated target has to be reachable from everywhere, not just from wherever you happened to test.

And here's the failure mode that ends careers. A delegated record stops resolving. Renewals start failing silently. Nobody notices, because the cert is still valid for now. Then it expires at 2am and takes a service down with it. The delegation that shrank your blast radius added a moving part, and moving parts fail without telling you. You want something watching the cert itself, the actual expiry on the actual endpoint, not just trusting that the renewal job exited zero. This is exactly the renewal-fails-then-silently-expires path CertPulse is built to catch. It probes the live endpoint and the certificate's real remaining lifetime, so a broken delegated record shows up as an alert with days to spare instead of an outage. When you're renewing nine times a year per name across a fleet, the question isn't whether a renewal will fail. It's whether you find out before the browser does.

Delegate the challenge, scope the credential to one record, keep the CAA and the monitoring on the real zone. That's DNS-01 with a blast radius you can live with.

This is why we built CertPulse

CertPulse connects to your AWS, Azure, and GCP accounts, enumerates every certificate, monitors your external endpoints, and watches Certificate Transparency logs. One dashboard for every cert. Alerts when auto-renewal fails. Alerts when certs approach expiry. Alerts when someone issues a cert for your domain that you didn't request.

If you're looking for complete certificate visibility without maintaining scripts, we can get you there in about 5 minutes.

DNS-01 without handing every host your DNS API keys: CNAME delegation done right | CertPulse