HTTP-01
The default challenge type. The client serves a token at a well-known path over
plain HTTP, and acme-proxy fetches it.
Not to be confused with the
http01upstream strategy. This page is aboutacme-proxyvalidating its own clients. The relay signer has achallenge_strategy = "http01"that runs the same challenge type in the opposite direction —acme-proxyserving the file, to prove itself to an upstream CA. The two share the well-known path and nothing else, and are configured independently.
How it works
- The client is given a random
tokenon the challenge object. - It computes the key authorization:
token + "." + base64url(SHA256(JWK thumbprint of its account key)). - It serves that string at
http://<identifier>/.well-known/acme-challenge/<token>. - It triggers the challenge with
POST /chall/{id}. acme-proxyresolves the identifier, fetches the URL, trims whitespace from the body, and compares it to the key authorization it computed independently.
The body is compared to the key authorization verbatim — unlike dns-01,
which compares a SHA-256 digest of it. Serving the digest here is a common
mistake when hand-rolling a responder.
Every ACME client implements a responder for this type, usually via a
--standalone mode or a webroot.
Limitations
- No wildcards.
*.example.comcannot be proven this way; usedns-01. - The name must resolve, and be reachable.
acme-proxyconnects to the client, usingdns.resolverto find it. A host behind a firewall that blocks inbound port 80 from the proxy cannot be validated.
Configuration
[challenge]
enabled = ["http-01"]
[challenge.http_01]
port = 80
https_port = 443
follow_redirects = true
max_redirects = 5
max_response_bytes = 4096
Reference
port (Integer) — Default: 80 | Env: ACME_PROXY_CHALLENGE__HTTP_01__PORT
Port the challenge is fetched from. RFC 8555 fixes this at 80 for the public Internet; it is configurable here because internal deployments frequently cannot bind low ports.
https_port (Integer) — Default: 443 | Env: ACME_PROXY_CHALLENGE__HTTP_01__HTTPS_PORT
Port used when a redirect sends the fetch to https.
follow_redirects (Boolean) — Default: true | Env: ACME_PROXY_CHALLENGE__HTTP_01__FOLLOW_REDIRECTS
Follow 3xx responses. Required by the specification, and commonly needed in practice — many hosts redirect all HTTP to HTTPS.
max_redirects (Integer) — Default: 5 | Env: ACME_PROXY_CHALLENGE__HTTP_01__MAX_REDIRECTS
Hop limit before the validation fails.
max_response_bytes (Integer) — Default: 4096 | Env: ACME_PROXY_CHALLENGE__HTTP_01__MAX_RESPONSE_BYTES
Cap on how much of the response body is read. A key authorization is under 100 bytes; this exists so a client cannot make the server read an unbounded stream.
The TLS certificate presented after a redirect to HTTPS is not validated (RFC 8555 §8.3) — at validation time the client by definition does not yet have a trusted certificate for the name.
Redirects are an SSRF surface
Following redirects means a client can steer the server’s fetch at an address of its choosing. Boulder’s usual mitigation — refusing to connect to RFC 1918 space — cannot apply here, because serving private networks is the entire point of this server.
What contains it instead:
- Only
httpandhttpsschemes are followed. - Only the two configured ports (
portandhttps_port) are connected to. - At most
max_redirectshops. - The shared
challenge.timeout_msbounds the whole attempt, redirects included. follow_redirects = falseturns the surface off entirely.- Nothing the fetch learned is echoed back to the client. The error a
client sees names its kind —
connectionorunauthorized— and the identifier, and says the server’s log has the reason. The status, the body length, the socket error and any redirect target stay inchallenge_validation_failed; a truncated body preview is logged atdebug. This is what stops the challenge from becoming a read primitive or a port scanner against your internal network. What remains is the kind itself: a client can still tell “nothing answered” from “something answered wrongly”.
If your threat model does not tolerate this, disable redirects, or use
dns-01, which makes no outbound connection to the client at all.
Troubleshooting
Look for these events in the log:
| Event | Meaning |
|---|---|
challenge_http_01_loaded | The fetch succeeded; a body was read. |
challenge_http_01_matched | The body matched. Validation passed. |
challenge_http_01_mismatch | The responder answered with the wrong content. Check that it is serving the key authorization, not just the token, and not the digest. |
challenge_http_01_redirect | A redirect was followed; the target is logged. |
challenge_validation_failed | The attempt failed. The detail says whether it was a connection error, a timeout, or a mismatch, with the status or socket error — the client is told only the kind. |
A connection failure usually means one of: the name does not resolve through
dns.resolver; nothing is listening on port; or a firewall blocks the proxy’s
egress to the client.