Skip to content
ecdsa.com

HTTP-01 vs DNS-01: choosing an ACME challenge

Both prove control of a name to the certificate authority. One does it through the web server, the other through DNS, and that decides which certificates you can get and which credentials you must keep.

HTTP-01 vs DNS-01 is the first decision when automating certificates with ACME (RFC 8555). With HTTP-01, the certificate authority fetches a token from your web server over port 80; with DNS-01, it looks up a TXT record in your zone. Both are domain validation methods under the Baseline Requirements and both give identical certificates, but they differ in what they can validate, where they can run and what can go wrong.

At a glance

HTTP-01DNS-01
SpecificationRFC 8555 §8.3; Baseline Requirements §3.2.2.4.19RFC 8555 §8.4; Baseline Requirements §3.2.2.4.7
What you proveControl of the web server answering for the nameControl of the DNS zone, or of the delegated _acme-challenge name
Where the proof goeshttp://name/.well-known/acme-challenge/tokenTXT record at _acme-challenge.name
ContentThe key authorization (token.thumbprint)base64url of its SHA-256 digest (43 characters)
Wildcard namesNot allowedAllowed
IP address certificatesAllowed (RFC 8738)Not possible
Network requirementInbound TCP port 80; Let's Encrypt follows up to 10 redirects to ports 80 or 443Only your authoritative DNS servers must be reachable
Internal or firewalled hostsNoYes
Many servers or anycastEvery node must serve the token, or proxy it to one responderOne record per name, independent of servers
Secrets on the serverNone beyond write access to the web rootDNS API credentials (scope them with CNAME delegation)
Typical failuresPort 80 blocked, redirect loops, CDN or WAF answering, stale AAAA recordsPropagation to secondary servers, broken DNSSEC, leftover records, API limits

What the CA actually checks

Both challenges use a key authorization: the challenge token joined with a dot to the JWK thumbprint (RFC 7638) of your ACME account key. Because the account key is part of the proof, someone who sees the token cannot complete the challenge for their own account. HTTP-01 serves the key authorization as is; DNS-01 publishes its SHA-256 digest, which fits in a TXT record. The script below reproduces both values for the example account key in RFC 7638 and the example tokens in RFC 8555 (Node.js 26).

Compute the HTTP-01 response and the DNS-01 record
import { createHash } from "node:crypto";

// Account public key: the RSA example JWK from RFC 7638 §3.1
const accountJwk = {
  kty: "RSA",
  e: "AQAB",
  n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
};

const b64url = (buf) => buf.toString("base64url");
const sha256 = (s) => createHash("sha256").update(s).digest();

// RFC 7638: hash the required members in lexicographic order
const { e, kty, n } = accountJwk;
const thumbprint = b64url(sha256(JSON.stringify({ e, kty, n })));
console.log("account key thumbprint:", thumbprint);

// http-01 (RFC 8555 §8.3): serve the key authorization itself
const httpToken = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0";
console.log("\nhttp-01");
console.log(`  GET http://www.example.org/.well-known/acme-challenge/${httpToken}`);
console.log(`  body: ${httpToken}.${thumbprint}`);

// dns-01 (RFC 8555 §8.4): publish base64url(SHA-256(key authorization))
const dnsToken = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA";
const txt = b64url(sha256(`${dnsToken}.${thumbprint}`));
console.log("\ndns-01");
console.log(`  _acme-challenge.www.example.org. 300 IN TXT "${txt}"`);
Output
account key thumbprint: NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs

http-01
  GET http://www.example.org/.well-known/acme-challenge/LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0
  body: LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0.NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs

dns-01
  _acme-challenge.www.example.org. 300 IN TXT "ZTRx1Ckl1-tM05o5zaizTTA0yUy5AGereMgSNWC6Ll8"

The thumbprint matches the value published in RFC 7638 §3.1, which confirms the computation. Clients do all of this for you; knowing the values helps when you debug a failing challenge by hand with curl or dig.

Multi-perspective checks and DNSSEC

Since 2025 public CAs must confirm each validation from several network locations (Baseline Requirements §3.2.2.9). Since 15 June 2026 at least four remote perspectives, at least 500 km apart and in at least two regional internet registry regions, take part, and only one may disagree; the minimum rises to five on 15 December 2026. For HTTP-01 this means every anycast node, GeoDNS answer and IPv4 or IPv6 address of the name must serve the token. For DNS-01 every authoritative name server must already have the record when you ask the CA to validate.

Since 15 March 2026 CAs must also validate DNSSEC on domain validation and CAA lookups (ballot SC-085v2). Unsigned zones are unaffected, but a signed zone with an expired signature or a broken DS record now blocks issuance for both challenge types; the DNSSEC inspector shows the chain the CA will check.

Keeping DNS credentials small

The main risk of DNS-01 is the credential it requires: a DNS API token on a web server can often rewrite the whole zone. Because CAs look up the TXT record with ordinary DNS resolution, which follows CNAME and NS delegation, the _acme-challenge name can point into a separate zone that the server is allowed to write, and nothing else:

Delegate only the challenge name
_acme-challenge.example.com.  3600  IN  CNAME  example-com.acme.example.net.

Tools such as acme-dns, or provider tokens scoped to one record or one zone, then limit what a stolen credential can do. Pair this with CAA records using the RFC 8657 parameters, which CAs must process from 15 March 2027:

CAA: one CA, one account, DNS validation only
example.com.  IN  CAA  0 issue "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890; validationmethods=dns-01"
example.com.  IN  CAA  0 issuewild "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890; validationmethods=dns-01"

With validationmethods in place, an attacker who briefly controls port 80 or a web path on your server can no longer obtain a certificate through HTTP-01. The CAA record generator writes these records.

TLS-ALPN-01 and persistent DNS records

TLS-ALPN-01 (RFC 8737) validates on port 443 inside the TLS handshake, using the acme-tls/1 protocol and a self-signed certificate that carries the key authorization digest. It suits TLS-terminating proxies and load balancers where port 80 is closed; like HTTP-01 it can validate IP addresses but not wildcards.

DNS-PERSIST-01 removes the per-renewal DNS change. Ballot SC-088v3 (October 2025) added "DNS TXT Record with Persistent Value" as Baseline Requirements §3.2.2.4.22: one long-lived TXT record at _validation-persist.name names the CA and your ACME account URI, optionally with policy=wildcard. The ACME challenge is still an IETF draft. Google Trust Services has supported draft 01 since 28 September 2026; Let's Encrypt has offered it only in staging and, as of October 2026, holds back production until an open issue in the draft is resolved. The trade-off is that the account key becomes a standing authorization, so protect it like a DNS credential.

Validation is about to happen on every renewal

Authorization reuse is shrinking with certificate lifetimes. The Baseline Requirements cap domain validation reuse at 200 days now, 100 days from March 2027 and 10 days from March 2029. Let's Encrypt's default profile reuses authorizations for 30 days today, 10 days from 10 February 2027 and 7 hours from 16 February 2028. Whichever challenge you choose, it must run unattended; copying TXT values by hand will not survive 2028.

When to choose which

  • One public web server with the ACME client on it: HTTP-01.
  • Wildcard certificates: DNS-01 (or DNS-PERSIST-01 where your CA offers it).
  • Internal hosts with public names, VPN-only services, development environments: DNS-01; remember the names will appear in CT logs.
  • Load-balanced fleets, multi-region or anycast deployments: DNS-01, or HTTP-01 with every node proxying /.well-known/acme-challenge/ to one responder.
  • CDNs and managed platforms: let the platform validate (usually HTTP-01 through its edge, or a CNAME you add once).
  • IP address certificates: HTTP-01 or TLS-ALPN-01; at Let's Encrypt only in the six-day shortlived profile.
  • Port 80 closed by policy: DNS-01 or TLS-ALPN-01.

Migration notes

  1. Moving from HTTP-01 to DNS-01: create a dedicated challenge zone, CNAME each _acme-challenge name into it, give the ACME client a token scoped to that zone and switch the client's challenge type; existing certificates keep working.
  2. Check propagation before answering the challenge: most clients can wait until all authoritative servers return the record; enable that rather than a fixed sleep.
  3. Clean up: clients should delete TXT records after validation (RFC 8555 §8.4); leftovers accumulate and slow lookups.
  4. Lock it down with CAA using accounturi and validationmethods, for both issue and issuewild.
  5. Renew on the CA's schedule: use ACME Renewal Information (ARI, RFC 9773) where supported, otherwise renew at about two thirds of the lifetime; the certificate lifetime calculator shows the limits by date.
  6. Watch issuance in the Certificate Transparency logs (Certificate Transparency for developers) so certificates obtained through any method are visible.

References

All comparisons