HTTP-01 challenge

HTTP-01 proves domain control by serving a file at a special URL on port 80. Simplest ACME challenge but breaks on firewalled or wildcard hostnames.

The HTTP-01 challenge is the ACME validation method that proves control of a hostname by serving a file over plain HTTP. The certificate authority requests http://example.com/.well-known/acme-challenge/<token> and expects the token plus a thumbprint of your ACME account key, joined by a dot.

It is the default in almost every ACME client, and it is the right default for a custom domain platform — Let's Encrypt's own documentation calls out that it "allows hosting providers to issue certificates for domains CNAMEd to them," which is exactly the SaaS case.

Domainee is a custom domains API for SaaS with a native MCP server — 50 domains and 100 GB free.

The flow

  1. Your client asks the CA for a certificate covering example.com.
  2. The CA returns a token and the expected response body.
  3. Your client arranges for http://example.com/.well-known/acme-challenge/<token> to return that body. In practice the web server or edge intercepts the path rather than a file being written to disk.
  4. Your client tells the CA to validate.
  5. The CA fetches the URL, from several network locations.
  6. On a match, the challenge is marked valid and issuance proceeds.

The reason this works for customer-owned domains is step 5. Once the customer has pointed their CNAME at your edge, the CA's request for their hostname arrives at your infrastructure. You answer it. The customer never touches DNS again, and never gives you access to their zone. Compare DNS-01, which needs write access to their zone and is therefore usually impossible in a BYOD design.

Port 80, and only port 80

The initial request is always to port 80. The ACME standard does not let the client nominate a different port, because being able to pick a high port would let anyone who can bind one claim the hostname.

That single constraint explains most HTTP-01 failures:

SituationHTTP-01
Port 80 firewalled, 443 openFails. The first request never lands.
Port 80 redirects to 443Works. See below.
Hostname not yet resolving to youFails. Nothing answers.
Wildcard certificate wantedNot possible. Use DNS-01.
Multiple edge nodes behind one hostnameWorks only if all of them can answer
CDN terminating TLS in front of youWorks if the challenge path reaches your origin, or the CDN runs its own ACME

The port-80-redirects case is worth stating plainly because people close port 80 believing HTTPS-only is stricter. Let's Encrypt follows redirects up to 10 deep, accepts only http: and https: targets, and only to ports 80 or 443. So the near-universal "redirect everything to HTTPS" configuration is fine. Closing port 80 entirely is not.

There is a detail inside that which surprises people: when the CA follows a redirect to HTTPS, it does not validate the certificate it finds there. It cannot, sensibly — the whole point of the exchange is to bootstrap a valid certificate, so it will routinely meet a self-signed or expired one on the way. That means an expired certificate on the target does not block renewal, which is what saves you when a renewal runs late.

Multi-perspective validation is the trap for multi-node edges

The CA does not fetch the token once. Since 2020 Let's Encrypt validates from its primary data centre path plus three remote network perspectives, and requires at least two of the three remote paths to agree. The design is deliberate: it means an attacker has to compromise three different network paths at the same time to fake a validation, work done with Princeton's research groups.

For a single server this is invisible. For a distributed edge it is a real constraint, and it is the most common way HTTP-01 fails in a SaaS that has already got everything else right:

Every node that can answer for the hostname must be able to serve the token. Requests from different perspectives can land on different anycast nodes or different regions. If your ACME client writes the token to local disk on the node that happened to run the renewal, a fetch from another perspective hits a node that has never heard of it, and the challenge fails — intermittently, which is worse than failing outright.

The fix is to keep challenge tokens in shared state rather than on the node that minted them, and to have every node serve /.well-known/acme-challenge/ from that shared store. Let's Encrypt states the requirement directly in its own list of drawbacks: if you have multiple web servers, the file has to be available on all of them.

The same reasoning applies to any load balancer or failover arrangement where the hostname can resolve to more than one address.

HTTP-01 against DNS-01

HTTP-01DNS-01
Proves control ofOne exact hostnameThe whole zone
NeedsPort 80 reaching youWrite access to the customer's DNS
WildcardsNoYes, and only this
Works before the host existsNoYes
SpeedImmediateBounded by TTL and propagation
Fits customer-owned domainsYesRarely
Fits your own tenant subdomainsYesYes

For a platform serving customer domains the split is usually clean: HTTP-01 for every customer hostname, DNS-01 on your own zone when you need a wildcard for tenant subdomains. Two paths, two failure modes, both worth monitoring separately.

Why issuance fails when everything looks right

A short list of causes that are not obvious from the error text:

  • The customer's CNAME is not live yet. The CA resolved the hostname before the record propagated. Verify resolution before requesting, not after — the CNAME lookup and propagation checker are the pre-flight.
  • An apex domain that was never pointed at you. The customer set up www and left the apex on their old host, so validation for the apex hits somebody else's server.
  • A catch-all redirect swallowing the challenge path. A rule that sends everything to / will happily send /.well-known/acme-challenge/... there too, and the CA gets a 200 with the wrong body. Exclude the path explicitly.
  • HTTP-to-HTTPS redirect chains longer than 10 hops, or a redirect to a non-80/443 port.
  • A dangling record from a departed customer, still resolving to you, still being renewed, still consuming issuance budget.
  • Rate limits. Let's Encrypt allows 50 new certificates per registered domain per 7 days. Renewals are exempt, so this bites on bursty onboarding rather than steady state.

Running it at scale: what to monitor

A platform holding thousands of single-name certificates is running HTTP-01 continuously, and the failures are quiet. A certificate that fails to renew does not page anyone until it expires, which is typically thirty days after the first failed attempt.

Four signals worth alerting on separately, because they have different causes:

SignalWhat it usually means
Validation failing for one hostnameThat customer's DNS changed or they left
Validation failing across many hostnames at onceYour edge stopped serving the challenge path, or shared token state is unreachable
Intermittent failures on one hostnameMulti-perspective fetches hitting nodes that cannot see the token
Issuance refused before validationRate limit, usually bursty onboarding

The gap between first failed renewal and actual expiry is the budget you have to fix things, so alert on the failure, not on the approaching expiry date. Monitoring only expiry means you find out with days left instead of weeks.

Two operational habits that prevent most of it. Renew well before expiry — the standard is at one third of remaining lifetime, which for a ninety-day certificate means around day sixty, leaving thirty days of retries. And remove hostnames when customers leave, because a departed customer's dangling record keeps consuming issuance attempts and rate-limit budget forever, and on shared infrastructure it is a subdomain takeover risk as well.

In a custom domain platform

The mechanism that makes HTTP-01 work here is SNI: one IP presents the right certificate per hostname, so the edge can hold thousands of single-name certificates and answer for each. The provisioning path detects a new hostname, confirms DNS points at the edge, requests the certificate, serves the token from shared state, and installs the result. Renewal then runs on the same path without the customer involved. The whole sequence, end to end, is in how SSL works for custom domains, and you can inspect what a host is actually serving with the SSL certificate checker.

FAQ

Can HTTP-01 issue a wildcard certificate? No. A wildcard covers names the CA never fetched, so serving a file on one hostname cannot justify it. Wildcards require DNS-01, which proves control of the zone rather than of one host.

Does HTTP-01 work if I redirect all HTTP traffic to HTTPS? Yes. Let's Encrypt follows up to 10 redirects and accepts http: and https: targets on ports 80 and 443, so the standard HTTPS redirect is fine. It also does not validate the certificate it lands on, so an expired or self-signed certificate at the destination will not block the challenge. What breaks it is closing port 80 completely.

Why does my HTTP-01 challenge fail only sometimes? Almost always multiple servers behind one hostname. The CA validates from several network perspectives, so different fetches can reach different nodes; if the token only exists on the node that requested the certificate, the others return 404. Serve the challenge path from shared state on every node.

Can I use a port other than 80 for HTTP-01? No. The ACME standard fixes it at port 80. Allowing arbitrary ports would let anyone able to bind a high port on the host claim the name, so no CA offers this as an option.

Which challenge should a SaaS use for customer domains? HTTP-01. Once the customer points their CNAME at your edge, validation requests for their hostname arrive at your infrastructure and you answer them without ever needing access to their DNS. DNS-01 would require credentials for a zone your customer controls, which is not a reasonable thing to ask for.

Does HTTP-01 work before the hostname points at me? No. The CA has to reach a server that answers for that exact name on port 80, so DNS has to be live first. If you need a certificate ahead of cutover, DNS-01 on a zone you control is the only option.

Want this handled for you? Start free with Domainee — 50 custom domains + 100 GB bandwidth, no card.