Secrets, PKI & CertificatesX · ACME and Certificate AutomationAutomation
HTTP-01, DNS-01 and TLS-ALPN-01 — how each one is actually validated
What you'll learn
- Describe exactly what an ACME validator fetches for each of the three challenge types
- Match a challenge type to the inbound network or DNS access it requires
- Explain why only DNS-01 can authorise a wildcard identifier
- Diagnose a failed challenge from client output and server-side validation logs
Prerequisites
Practice
Verified against OpenSSL 3.5.x teaching target; 3.0+ minimum · OpenSSH 10.x teaching target; 8.2+ minimum for certificate workflows · OpenBao 2.6.x · Smallstep step-ca 0.30.x · Certbot / Pebble Certbot current release; Pebble 2.10.x ACME test server · Kubernetes (cross-course target) 1.36.x · PostgreSQL 17.x · 2026-08-26
A challenge is the part of ACME where a claim becomes evidence. The client asserts that it controls a name; the certificate authority makes an independent observation, from its own network position, to see whether that assertion holds. All three current challenge types test the same secret value. What differs is which port has to be open, which credential has to exist, and what breaks when the observation does not match.
One proof, three transports
Every challenge carries a server-generated token with at least 128 bits of entropy, and the answer the validator looks for is the key authorization: the token, a full stop, and the base64url thumbprint of the account public key. Because that thumbprint is part of the answer, publishing it proves two things at once, that you control the name and that you are the specific ACME account asking. A token harvested from someone else’s challenge is worthless.
The three types differ in what they wrap that value in:
- HTTP-01 serves it as the literal body of a file, in ASCII, at a fixed path.
- DNS-01 publishes the SHA-256 digest of it, base64url encoded, in a TXT record.
- TLS-ALPN-01 embeds the SHA-256 digest of it in a critical X.509 extension of a self-signed certificate presented during a handshake.
HTTP-01 is a plain HTTP fetch on port 80
The validator requests the path /.well-known/acme-challenge/
followed by the token, and the response body must be the ASCII
representation of the key authorization with nothing else in it.
The specification insists this happens over plain HTTP rather
than HTTPS, and gives the reason: many web servers hand the
default HTTPS virtual host to a particular low-privilege tenant
in ways that are subtle and non-intuitive, so validating over
HTTPS would let the wrong tenant answer.
Let’s Encrypt narrows it further. The request goes to port 80
only. Redirects are followed up to ten deep, but only to http:
or https: schemes and only to ports 80 or 443, which is what
makes the common pattern of redirecting everything to HTTPS
compatible with validation. Wildcards cannot be issued this way.
The operational requirement is therefore an inbound TCP/80 path from the public internet to something you control, which is often the thing an organisation has spent years closing. Test it the way the validator will:
# Fetch the challenge path exactly as a validator would: plain
# HTTP on port 80, following redirects, from outside your estate.
NAME=api.example.com
TOKEN=probe-value-not-a-real-token
curl -sS -i -L --max-redirs 10 \
"http://$NAME/.well-known/acme-challenge/$TOKEN"
A 404 with the right headers proves the path reaches your server and that the ACME client will be able to place a file there. A connection timeout, a redirect to a port other than 80 or 443, or a response from a captive proxy all mean the challenge will fail before your client is ever at fault.
DNS-01 is a TXT record, and the only route to a wildcard
For DNS-01 the client prepends the label _acme-challenge to
the name being validated and publishes a TXT record there
holding the base64url encoding of the SHA-256 digest of the key
authorization. The specification’s own worked example shows the
shape:
_acme-challenge.www.example.org. 300 IN TXT "gfj9Xq...Rg85nM"
The validator computes the digest it expects, queries TXT for
that name, and checks that the contents of one of the returned
records match. That the rule is “one of” rather than “the only
one” matters enormously in practice: a single order covering
example.com and *.example.com produces two authorizations
whose validation name is identical, so two TXT records must
coexist at _acme-challenge.example.com simultaneously. A
client or a DNS provider integration that replaces rather than
appends will break exactly this case, and only this case, which
is why it survives testing.
DNS-01 is the only challenge type that can authorise a wildcard
identifier. The authorization the server returns for a wildcard
omits the leading asterisk and full stop and instead carries a
wildcard field set to true. Neither HTTP-01 nor TLS-ALPN-01
can produce one, and pre-authorization cannot either.
Its access requirement is a credential that can write records in
your authoritative DNS, which is a far more powerful thing to
hand an automation host than the ability to serve a file. The
standard mitigation is delegation: publish a CNAME from
_acme-challenge.api.example.com into a small dedicated zone,
and give the ACME client write access only to that zone. The
credential can then create validation records and nothing else.
# Check propagation from more than one resolver before answering
# the challenge; the validator will not use yours.
NAME=api.example.com
dig +short TXT "_acme-challenge.$NAME" @198.51.100.53
dig +short TXT "_acme-challenge.$NAME" @203.0.113.53
TLS-ALPN-01 puts the proof inside the handshake
RFC 8737 defines a challenge that requires no HTTP server and no
DNS credential. The validator opens a TLS connection to TCP port
443 for the name being validated, offering the single ALPN
protocol identifier acme-tls/1. The responder must select that
protocol and present a self-signed certificate containing a
subjectAltName with exactly one dNSName entry, plus an
acmeIdentifier extension that is marked critical and carries the
SHA-256 digest of the key authorization.
The critical flag is the safety property. Any TLS stack that does not understand the extension must reject the certificate, so the validation certificate cannot be mistaken for a normal serving certificate by anything else. Wildcards are not available here either.
TLS-ALPN-01 suits an operator who terminates TLS themselves and cannot open port 80, and it does not suit anyone whose TLS is terminated by a load balancer or CDN they do not control, because the responder has to sit exactly where the handshake is terminated. Let’s Encrypt’s own guidance is blunt: this challenge is not suitable for most people.
Choosing, and reading the failure
flowchart TD
A{"Do you need a wildcard\nor an internal-only name?"} -- "yes" --> D["DNS-01"]
A -- "no" --> B{"Is inbound TCP 80\nreachable from the internet?"}
B -- "yes" --> H["HTTP-01"]
B -- "no" --> C{"Do you control the process\nthat terminates TLS on 443?"}
C -- "yes" --> T["TLS-ALPN-01"]
C -- "no" --> D
The decision reduces to three questions asked in order. A wildcard, or a name that resolves only inside your network, forces DNS-01 because it is the only method that never requires the validator to reach the host. Otherwise an open inbound TCP/80 path makes HTTP-01 the cheapest option to operate. Failing that, TLS-ALPN-01 is available if and only if you own the TLS termination point, and if you do not, you are back to DNS-01 whether you wanted the DNS credential or not.
| HTTP-01 | DNS-01 | TLS-ALPN-01 | |
|---|---|---|---|
| Inbound port | TCP 80 | none | TCP 443 |
| Credential needed | write a file | authoritative DNS write | control TLS termination |
| Wildcards | no | yes | no |
| Specification | RFC 8555 | RFC 8555 | RFC 8737 |
| IP identifiers | yes | no | yes |
When it goes wrong, the client tells you the validator’s view, not yours. This is a real failure where the name did not resolve at all:
Type: connection
Detail: Get "http://unreachable.lab.example:80/.well-known/acme-challenge/XLMIGp112V_...": error
occurred while resolving URL "...": lookup unreachable.lab.example on 127.0.0.11:53: no such host
Hint: The Certificate Authority failed to download the challenge files from the temporary standalone
webserver started by Certbot on port 80. Ensure that the listed domains point to this machine and that it
can accept inbound connections from the internet.
Read the Type field first. connection means the validator
could not establish a conversation at all, so the fault is
resolution, routing or firewalling, and no amount of adjusting
the file on your web server will help.
Production discipline
- Choose the challenge from your network reality, not from a tutorial. The type you pick becomes a permanent dependency on either a firewall rule or a DNS credential, and changing it later is a migration.
- Delegate the validation label. A CNAME from
_acme-challengeinto a dedicated zone keeps the automation credential away from the records that carry your traffic. - Verify propagation from resolvers you do not run. Your own resolver may hold a stale or locally overridden answer; the validator will use neither.
- Never de-provision the TXT record before the authorization is valid. Removing it during validation produces an intermittent failure that reproduces about half the time.
- Keep the challenge path out of authentication. A single
sign-on redirect or an IP allowlist in front of
/.well-known/acme-challenge/turns every renewal into an outage on a schedule you chose months earlier.
Cross-course references
- Linux for Production Sysadmins - Part XXIII (DNS) covers authoritative DNS, TTLs and caching behaviour, which is the machinery that decides whether a DNS-01 record has actually propagated.
- Observability for Production Sysadmins - Part XI (Blackbox) covers DNS and TCP probes, the same external observation model an ACME validator applies to your challenge.
- Kubernetes for Production Sysadmins - Part XLII (Ingress) covers the HTTP gateway that must pass the challenge path through unauthenticated for HTTP-01 to work in a cluster.
Quiz
Knowledge check · 4 questions
Q1. An order requests both example.com and *.example.com. What must be true of the DNS during validation?
Q2. TLS-ALPN-01 requires the responder to sit at the point where TLS is terminated on port 443, which makes it unusable when a load balancer or CDN you do not control terminates TLS.
Q3. State why HTTP-01 validation is performed over plain HTTP on port 80 rather than over HTTPS.
Q4. Explain why an intermittently failing HTTP-01 challenge is not a client problem.
On 2026-08-26 an operator renews the certificate for api.example.com using HTTP-01. Roughly one attempt in three succeeds. From a laptop on the office network, fetching the challenge path over plain HTTP on port 80 works every time. The name is served by four edge nodes behind anycast, and a configuration change was rolled out to three of them the previous evening.
Passing score: 75%. Answers are checked in this browser.