Operations

How the ACME protocol works: RFC 8555, challenges and ARI

By · Last verified

ACME (Automatic Certificate Management Environment) is the protocol that lets software request, validate, renew and revoke TLS certificates without a person in the loop. Standardized as RFC 8555 in March 2019, it is a JSON API over HTTPS: the account key signs every request, challenges such as http-01 and dns-01 prove domain control, and a CSR completes the order.

What ACME automates

ACME replaces the manual steps of buying a certificate with an API that a cron job can drive. Before ACME, a request meant generating a CSR, pasting it into a web form, clicking a link in an email or uploading a file by hand, and installing the result manually. That took minutes per certificate and failed silently when someone forgot.

Let's Encrypt built ACME for its 2015 launch, and the IETF finished the standard in 2019. Every major free CA and most commercial CAs now support it. The CA/Browser Forum Baseline Requirements recognise the ACME validation methods directly: section 3.2.2.4.19 covers the ACME http-01 method, 3.2.2.4.7 covers DNS changes such as dns-01, and 3.2.2.4.20 covers TLS-ALPN. With certificate lifetimes falling to 47 days by 2029, ACME has gone from convenient to effectively mandatory; see automating renewal.

The directory and its resources

Every ACME server publishes a single directory URL, and clients discover everything else from it. This is the Let's Encrypt production directory as of November 2026:

{
  "keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
  "meta": {
    "caaIdentities": ["letsencrypt.org"],
    "profiles": {
      "classic": "https://letsencrypt.org/docs/profiles#classic",
      "shortlived": "https://letsencrypt.org/docs/profiles#shortlived",
      "tlsserver": "https://letsencrypt.org/docs/profiles#tlsserver"
    },
    "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.8-July-06-2026.pdf",
    "website": "https://letsencrypt.org"
  },
  "newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct",
  "newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce",
  "newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order",
  "renewalInfo": "https://acme-v02.api.letsencrypt.org/acme/renewal-info",
  "revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert"
}
Resource Purpose
newNonce Returns a fresh anti-replay nonce in the Replay-Nonce header
newAccount Creates or finds an account bound to a public key
newOrder Starts a request for a certificate covering one or more identifiers
Authorization One per identifier; holds the challenges for that name
Challenge A specific way to prove control (http-01, dns-01, tls-alpn-01)
finalize Per-order URL where the client submits the CSR
Certificate Per-order URL to download the issued chain
revokeCert Revokes a certificate
keyChange Rolls the account over to a new key
renewalInfo ARI endpoint added by RFC 9773

The caaIdentities value is the string you put in a CAA record to authorize this CA.

Every request is a signed JWS

ACME authenticates requests by signature, not by password or API token. Each POST body is a JSON Web Signature whose protected header names the algorithm, a nonce, the exact URL being called, and the account:

{
  "protected": base64url({
    "alg": "ES256",
    "kid": "https://acme-v02.api.letsencrypt.org/acme/acct/1234567890",
    "nonce": "918MM8jNCGhHSIKmADOgmJbHgyvZRtj4QF5fM6dr95rPfCqLw7A",
    "url": "https://acme-v02.api.letsencrypt.org/acme/new-order"
  }),
  "payload": base64url({"identifiers": [{"type": "dns", "value": "example.com"}]}),
  "signature": "..."
}

Three rules follow from this design. First, the newAccount request carries the full public key in a jwk field, and every later request refers to the account by its URL in kid. Second, each nonce is single-use; if the server rejects one with a badNonce error, the client takes the new nonce from the response and retries. Third, reads are also signed: ACME uses POST-as-GET, a POST with an empty payload, to fetch orders and certificates, so the server always knows which account is asking.

Accounts and External Account Binding

An ACME account is a key pair registered with the CA. The newAccount request can include contact addresses and must signal agreement to the terms of service. Let's Encrypt no longer sends expiry emails since June 4, 2025, so the contact address matters less than it used to.

Commercial CAs often require External Account Binding (EAB), defined in RFC 8555 section 7.3.4. The CA gives you a key identifier and an HMAC key from its web console, and the client uses them to prove that the new ACME account belongs to a paying customer. The account key is separate from any certificate key and can be rotated with keyChange without reissuing certificates.

Orders, authorizations and the state machine

An order is a request for one certificate, and it moves through a fixed set of states. The client posts the identifiers it wants to newOrder, receives one authorization per identifier, completes a challenge for each, then finalizes with a CSR.

              newOrder
                 |
                 v
   +---------> pending --------------------+
   |             |  all authorizations     |  any authorization
   |             |  become valid           |  fails or expires
   |             v                         v
   |           ready  --(finalize + CSR)--> invalid
   |             |                         ^
   |             v                         |
   |         processing ---(CA error)------+
   |             |
   |             v
   |           valid  --> download certificate chain
   |
 Authorization: pending -> valid | invalid ; valid -> expired | deactivated | revoked
 Challenge:     pending -> processing -> valid | invalid

A valid authorization can be reused for later orders from the same account for a limited time. Under Ballot SC-081v3 the Baseline Requirements cap that reuse period at 200 days, falling to 100 days from March 15, 2027 and 10 days from March 15, 2029. Let's Encrypt is stricter: its tlsserver and shortlived profiles reuse authorizations for only 7 hours.

The three challenge types

All three challenges are built on the same key authorization string, which ties the CA's random token to your account key:

keyAuthorization = token + "." + base64url( SHA-256 JWK thumbprint of the account key )
http-01 dns-01 tls-alpn-01
Where the proof lives http://<domain>/.well-known/acme-challenge/<token> TXT record at _acme-challenge.<domain> Self-signed certificate served on port 443
Content The key authorization itself base64url(SHA-256(keyAuthorization)) Critical acmeIdentifier extension (OID 1.3.6.1.5.5.7.1.31) containing SHA-256(keyAuthorization)
Port 80 only DNS 443, ALPN acme-tls/1
Wildcards No Yes No
IP addresses Yes No Yes
Typical user Single web server Wildcards, internal hosts, many servers TLS proxies and hosting platforms

http-01 is the default for most clients. Let's Encrypt follows up to 10 redirects, but only to ports 80 or 443, and does not validate the certificate when redirected to HTTPS. Every server behind a load balancer must serve the same file.

dns-01 proves control of the zone. It is the only way to obtain a wildcard certificate, and it works for hosts that are not exposed to the internet. Because Let's Encrypt resolves TXT records normally, you can delegate _acme-challenge with a CNAME to a zone that is easier to automate. The cost is that DNS API credentials end up in your issuance pipeline.

tls-alpn-01, specified in RFC 8737, replaced the insecure tls-sni-01 method that Let's Encrypt removed in March 2019. The server must recognise the special ALPN protocol and present a validation certificate only for those connections, which is why it suits reverse proxies more than ordinary web servers.

Let's Encrypt validates from several network locations at once. That practice became an industry rule through Ballot SC-067, which requires Multi-Perspective Issuance Corroboration (MPIC) for domain validation and CAA checks, making localized BGP hijacks much harder to exploit.

Finalize, download and revoke

Once every authorization is valid, the order becomes ready and the client posts a CSR to the finalize URL. The CSR's names must match the order exactly. The server moves the order to processing, then valid, and the client downloads the chain from the certificate URL as application/pem-certificate-chain. If a CA offers alternate chains, it lists them in Link: rel="alternate" headers.

Revocation goes to revokeCert and can be signed either by the account that ordered the certificate or by the certificate's own private key. The second option lets anyone who holds a leaked key revoke the certificate even without the account.

ARI: the CA tells you when to renew

ACME Renewal Information, published as RFC 9773, adds a renewalInfo endpoint that returns a suggested renewal window for a specific certificate. The certificate is identified by the base64url key identifier from its Authority Key Identifier, a dot, and its base64url serial number:

GET /acme/renewal-info/aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE

HTTP/1.1 200 OK
Retry-After: 21600
{
  "suggestedWindow": {
    "start": "2025-01-02T04:00:00Z",
    "end":   "2025-01-03T04:00:00Z"
  },
  "explanationURL": "https://acme.example.com/docs/ari"
}

The client picks a random time inside the window, renews then, and re-checks after the Retry-After interval. When it orders the replacement it includes a replaces field naming the old certificate. A CA can move the window earlier when it needs to revoke certificates in bulk, so clients that honour ARI renew before the revocation instead of after an outage. Let's Encrypt recommends checking ARI at least twice a day.

Profiles: choosing what kind of certificate you get

Profiles let a client choose between certificate variants that a CA offers. The CA lists them under meta.profiles in the directory, and the client adds "profile": "tlsserver" to its newOrder request. The mechanism is the IETF ACME working group draft ACME Profiles Extension (draft-ietf-acme-profiles), which Let's Encrypt implements.

Let's Encrypt profile Validity Authorization reuse Notes
classic (default) 90 days; 64 days from Feb 10, 2027; 45 days from Feb 16, 2028 30 days Includes Common Name; Key Encipherment for RSA keys
tlsserver 45 days since May 13, 2026 7 hours No Common Name, no Subject Key ID, max 25 names
shortlived 160 hours 7 hours Same as tlsserver; also allows IP addresses

Certificates from Let's Encrypt's Generation Y hierarchy carry only the server-authentication EKU, so ACME certificates from Let's Encrypt are no longer suitable for TLS client authentication; see mTLS.

ACME clients

In practice a client such as Certbot, acme.sh or lego speaks ACME for you, or the web server does it itself. The table lists widely used open-source clients and servers with built-in ACME. ssl.sh is not affiliated with any of them.

Client Language / form Notes
Certbot Python CLI --preferred-profile since Certbot 4.0; many DNS plugins
acme.sh POSIX shell Large set of DNS API hooks; no root required
lego Go CLI and library Many DNS providers
Caddy Web server Automatic HTTPS by default
nginx ngx_http_acme_module Web server module Official module announced in August 2025
cert-manager Kubernetes controller Issuer and Certificate resources

Errors you will meet

ACME errors are problem documents with types under urn:ietf:params:acme:error:. The most common ones map to concrete fixes:

Error type Usual cause
badNonce Stale nonce; the client should retry automatically
unauthorized Challenge content wrong, or wrong server answered
connection CA could not reach port 80 or 443; see certbot timeout
dns NXDOMAIN, SERVFAIL or missing TXT record
caa A CAA record forbids this CA; see CAA errors
rateLimited Too many orders or failures; see rate limits
rejectedIdentifier The CA will not issue for this name

Test against the CA's staging directory first. Staging certificates are not trusted, but staging has higher rate limits and shows the same errors, so a broken setup fails there instead of burning production limits.

FAQ

Which ACME challenge should I use?

Use http-01 if the server answers on port 80 for the name you need; it is the easiest to automate. Use dns-01 for wildcard certificates, for hosts that are not reachable from the internet, or when many servers share a name. tls-alpn-01 is mainly for TLS-terminating proxies and hosting platforms that cannot use port 80.

Is ACME only for Let's Encrypt?

No. ACME is an IETF standard. Google Trust Services, ZeroSSL, Sectigo, DigiCert and others offer ACME endpoints, and commercial CAs often require External Account Binding (EAB) so that the ACME account is tied to a customer account. Private CAs such as step-ca and HashiCorp Vault also speak ACME.

What is the difference between the account key and the certificate key?

The account key signs ACME requests and identifies you to the CA; it never appears in a certificate. The certificate key is the key in your CSR that the server uses for TLS. Keep them separate so that rotating one does not affect the other, and use keyChange to roll over the account key.

Do I need ARI if my client already renews at one third of the lifetime?

A one-third threshold is a good backstop, but only ARI tells you when a CA plans a mass revocation or wants to spread load. Let's Encrypt recommends checking ARI at least twice a day and renewing at one third of the lifetime if ARI is unavailable.

Sources

  1. RFC 8555: Automatic Certificate Management Environment (ACME)
  2. RFC 8737: ACME TLS-ALPN challenge extension
  3. RFC 9773: ACME Renewal Information (ARI) Extension
  4. IETF draft: ACME Profiles Extension
  5. Let's Encrypt: Challenge Types
  6. Let's Encrypt: Profiles
  7. CA/Browser Forum Ballot SC067v3: Multi-Perspective Issuance Corroboration