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-01 | DNS-01 | |
|---|---|---|
| Specification | RFC 8555 §8.3; Baseline Requirements §3.2.2.4.19 | RFC 8555 §8.4; Baseline Requirements §3.2.2.4.7 |
| What you prove | Control of the web server answering for the name | Control of the DNS zone, or of the delegated _acme-challenge name |
| Where the proof goes | http://name/.well-known/acme-challenge/token | TXT record at _acme-challenge.name |
| Content | The key authorization (token.thumbprint) | base64url of its SHA-256 digest (43 characters) |
| Wildcard names | Not allowed | Allowed |
| IP address certificates | Allowed (RFC 8738) | Not possible |
| Network requirement | Inbound TCP port 80; Let's Encrypt follows up to 10 redirects to ports 80 or 443 | Only your authoritative DNS servers must be reachable |
| Internal or firewalled hosts | No | Yes |
| Many servers or anycast | Every node must serve the token, or proxy it to one responder | One record per name, independent of servers |
| Secrets on the server | None beyond write access to the web root | DNS API credentials (scope them with CNAME delegation) |
| Typical failures | Port 80 blocked, redirect loops, CDN or WAF answering, stale AAAA records | Propagation 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).
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}"`);
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:
_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:
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
shortlivedprofile. - Port 80 closed by policy: DNS-01 or TLS-ALPN-01.
Migration notes
- Moving from HTTP-01 to DNS-01: create a dedicated challenge zone, CNAME each
_acme-challengename into it, give the ACME client a token scoped to that zone and switch the client's challenge type; existing certificates keep working. - Check propagation before answering the challenge: most clients can wait until all authoritative servers return the record; enable that rather than a fixed sleep.
- Clean up: clients should delete TXT records after validation (RFC 8555 §8.4); leftovers accumulate and slow lookups.
- Lock it down with CAA using
accounturiandvalidationmethods, for bothissueandissuewild. - 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.
- Watch issuance in the Certificate Transparency logs (Certificate Transparency for developers) so certificates obtained through any method are visible.
References
- RFC 8555 §8, ACME identifier validation challenges
- RFC 8737, TLS-ALPN-01 and RFC 8738, ACME IP identifiers
- RFC 8657, CAA accounturi and validationmethods
- RFC 7638 §3.1, JWK thumbprint example
- RFC 9773, ACME Renewal Information
- CA/Browser Forum Baseline Requirements §3.2.2.4, §3.2.2.9, §4.2.2
- Ballot SC-085v2, DNSSEC validation for CAA and DCV lookups
- Let's Encrypt: challenge types
- Let's Encrypt: DNS-PERSIST-01
- Let's Encrypt: decreasing certificate lifetimes to 45 days