Part VI: Running PKI · Chapter 20
ACME and the 47-day schedule
In one sentence
ACME lets software prove control of a name, then fetch, renew and install certificates automatically: the only practical way to live with very short certificate lifetimes.
Who is this for
Level: Practitioner
On this page
The idea
- Beginner
Getting a certificate by hand takes many steps. You create a key and a CSR, paste the CSR into a CA’s web portal, prove that you control the domain, download the certificate, install it with its chain, reload the server and set a reminder for next time. Done once a year, that is tolerable. Done at least eight times a year for every name, across hundreds of names, it is not.
ACME, the Automatic Certificate Management Environment, is an open protocol that lets software do all of it. A program on your side, the ACME client, talks to the CA’s ACME server. The client:
- creates an account with the CA;
- orders a certificate for one or more names;
- proves it controls those names by answering a challenge, for example by publishing a token at a web address or in DNS;
- sends a CSR and downloads the signed certificate;
- installs it, reloads the service and starts again before the certificate expires.
The private key never leaves your server. ACME was built for Let’s Encrypt and is now an internet standard, RFC 8555. Many public CAs support it, and so do private CA products, so the same tools work for internal certificates too.
ACME does not remove the need for care. A renewal robot that fails quietly is worse than a person with a calendar, because everyone assumes it works. Automation and monitoring go together (see Inventory, expiry and renewal).
How it really works
- Developer
- Expert
What happens in an ACME exchange?
Every ACME request is an HTTPS POST whose body is signed with the client’s account key, a key pair that belongs to the ACME account and is separate from the certificate’s key. Each request also carries a fresh one-time value, a nonce, so that a recorded request cannot be replayed (RFC 8555 section 7.2). The client finds every endpoint in the server’s directory (section 7.1.1). This is Let’s Encrypt’s real directory:
curl -s https://acme-v02.api.letsencrypt.org/directory
{
"Gzv7tmjYRzs": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
"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"
}
The odd first entry is deliberate: Let’s Encrypt adds a random key so that clients learn to ignore fields they do not know. Then the exchange runs like this:
- Account (section 7.3). The client registers its account key with a POST to
newAccount. Commercial CAs often link this to an existing customer account with External Account Binding (section 7.3.4). - Order (section 7.4). The client lists the names it wants. An order moves through the states
pending,ready,processingandvalid, or ends asinvalid(section 7.1.6). - Authorization (section 7.5). For each name, the server offers one or more challenges.
- Challenge (section 7.5.1). The client publishes a key authorization, the challenge token joined to a hash of its account key (section 8.1), and tells the server it is ready. The CA checks it from the internet.
- Finalize. Once every authorization is valid and the order is
ready, the client sends its CSR to the order’s finalize URL. - Download (section 7.4.2). The client fetches the certificate with its chain in PEM format.
How does the CA check that you control the name?
There are three standard challenge types:
| Challenge | What the CA checks | Port | Wildcards | Good fit |
|---|---|---|---|---|
| HTTP-01 (RFC 8555 section 8.3) | A file at http://<name>/.well-known/acme-challenge/<token> |
80 | No | One web server, or a load balancer that serves the file |
| DNS-01 (section 8.4) | A TXT record at _acme-challenge.<name> holding the base64url SHA-256 digest of the key authorization |
none | Yes | Wildcards, many servers, names that are not reachable from the internet |
| TLS-ALPN-01 (RFC 8737) | A special certificate offered on a TLS connection that negotiates the protocol acme-tls/1 |
443 | No | When port 80 is closed and the client controls the TLS endpoint |
Let’s Encrypt’s FAQ is explicit: “Wildcard issuance must use the DNS-01 challenge.” Its challenge-types page lists the trade-offs, and warns that “Keeping API credentials on your web server is risky”. You can limit that risk: the same page says “you can use CNAME records or NS records to delegate answering the challenge to other DNS zones”. Point _acme-challenge at a small zone that holds nothing else, and give the client an API key for that zone only.
Publicly trusted CAs must also check domain control from several network locations, not just one (Baseline Requirements section 3.2.2.9). This makes it much harder to fool the CA by hijacking the network path to a single location.
When should a client renew?
Old clients renewed on a fixed schedule, such as a monthly cron job, or a fixed number of days before expiry. ACME Renewal Information, ARI, replaces that guesswork. It is RFC 9773, on the IETF standards track. The CA publishes a renewalInfo endpoint, and for each certificate it suggests a renewal window. RFC 9773 explains why: a CA “could suggest that clients renew prior to a mass-revocation event to mitigate the impact of the revocation”, or spread out a coming spike of renewals. The recommended client behaviour is to “select a uniform random time within the suggested window”.
Anyone can ask. The certificate is identified by its Authority Key Identifier and serial number, both base64url-encoded and joined with a dot. This is the real answer for the letsencrypt.org certificate seen in this handbook:
curl -s https://acme-v02.api.letsencrypt.org/acme/renewal-info/uVnyjs8i8IbTN0j_dhQYuoLYVYc.BUOTO-OGs6KzrdU08hA7yLZf
{
"suggestedWindow": {
"start": "2026-11-02T17:18:36Z",
"end": "2026-11-04T12:29:25Z"
}
}
The certificate runs from 4 September to 3 December, so the window falls about two-thirds of the way through its life. Let’s Encrypt’s integration guide asks clients to check ARI “at least twice a day”. Without ARI, it recommends renewing “when they have a third of their total lifetime left”, or halfway through for very short-lived certificates.
What does the shrinking schedule change for ACME?
Two limits shrink together. Certificates will last at most 47 days from 15 March 2029, and a CA may reuse a domain validation for only 10 days from 15 March 2029. So nearly every renewal will need a fresh challenge. Your challenge path, whether the web root, the DNS API or port 443, must work every single time, not just on the day you set it up.
Let’s Encrypt has published its own path (announcement). Today its default is 90 days. It offers 45-day certificates in its opt-in tlsserver profile from 13 May 2026. The default falls to 64 days from 10 February 2027, then to 45 days from 16 February 2028, when its authorization reuse also drops to 7 hours. It also offers six-day certificates to those who opt in. The real directory above shows the three profiles, classic, tlsserver and shortlived, and a client picks one when it orders.
What are rate limits?
Public CAs limit how often one account or domain may request certificates, to protect the service. Let’s Encrypt’s current limits (rate limits) include up to 50 certificates per registered domain every 7 days, up to 5 certificates for the exact same set of names every 7 days and up to 5 failed validations per name, per account, per hour. A healthy client renewing on time never comes close. A broken script that retries in a tight loop hits them fast. Two habits avoid trouble:
- Test against staging. Let’s Encrypt recommends its staging environment “when testing or troubleshooting your applications”.
- Use ARI. “Renewals coordinated by ARI offer the unique benefit of being exempt from all rate limits.”
Can I run ACME for internal names?
Yes. A private CA can speak ACME too. For example, smallstep’s step-ca “works with any ACME-compliant (specifically, ACMEv2; RFC8555) client”, and by default it issues certificates with “much shorter 24 hour lifetimes” (smallstep). You point the same clients at your own directory URL. The CA can still validate names your public DNS never sees, as long as it can reach them. The private CA chapter covers when that is a good idea.
At work
- Developer
- IT ops
Which client should I use?
| Client | Where it fits | ARI support |
|---|---|---|
| certbot | Linux servers, with nginx and Apache plugins | Yes, since certbot 4.1.0 |
| acme.sh | Any Unix shell, no dependencies, many DNS APIs. Its default CA is ZeroSSL, so set the CA you want explicitly | Yes, on by default since acme.sh 3.1.4 |
| lego | A single Go binary, and a Go library | Yes (RFC 9773 listed) |
| cert-manager | Kubernetes: certificates as resources, stored in secrets | Experimental: off by default, behind the ACMEUseARI feature gate (alpha since v1.21) |
| win-acme | Windows and IIS; it creates a scheduled task for renewal | Yes, based on a draft of the ARI specification |
How do I test certbot safely?
sudo certbot renew --dry-run
--dry-run performs “a test run against the Let’s Encrypt staging server, obtaining test (invalid) certificates but not saving them to disk”. Run it after every change to DNS, firewalls or web server configuration. Most packages install a timer or cron job that runs certbot renew regularly. Check that it exists with systemctl list-timers.
Then make sure the service picks up the new certificate. A deploy hook runs “once for each successfully issued certificate, including on subsequent renewals”:
sudo certbot renew --deploy-hook "systemctl reload nginx"
Point your server at fullchain.pem, not cert.pem. The certbot documentation says fullchain.pem “is what Apache >= 2.4.8 needs for SSLCertificateFile, and what Nginx needs for ssl_certificate”. Serving cert.pem alone leaves out the intermediate. Browsers often hide that mistake, but curl, Java and Python fail with errors like unable to get local issuer certificate or verify error 21.
How do I do it in Kubernetes?
With cert-manager, you declare an issuer once and a Certificate per name. This example uses Let’s Encrypt’s staging directory and the HTTP-01 challenge through an nginx ingress:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: pki-team@example.com
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- http01:
ingress:
ingressClassName: nginx
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: www-example-com
namespace: my-app
spec:
secretName: www-example-com-tls
dnsNames:
- www.example.com
issuerRef:
name: letsencrypt-staging
kind: ClusterIssuer
cert-manager stores the key and certificate in the secret www-example-com-tls and renews by default “2/3 through the X.509 certificate’s duration” (cert-manager). Switch the server to the production directory once staging works. Check progress with kubectl get certificate -n my-app and kubectl describe certificate www-example-com -n my-app.
How do I point a client at a private ACME server?
Most clients take a directory URL. With certbot, add --server:
sudo certbot certonly --webroot -w /var/www/html \
--server https://ca.internal/acme/acme/directory -d app.internal.example
The client host must trust the private CA’s root to talk to that server over HTTPS.
The lifetime calendar shows how often your certificates renew under each step of the schedule:
Common mistakes
- “ACME is a Let’s Encrypt thing.” It is an internet standard. Many public CAs and private CA products speak it, so one set of tools covers all your certificates.
- “HTTP-01 can get me a wildcard.” Wildcards need DNS-01.
- “The renewal job runs, so we are covered.” Only if the service reloads the new certificate and your monitoring checks the live endpoint.
- “I’ll test against production.” Use staging or
--dry-run. A broken script that loops against production quickly hits the rate limits. - “Renew on the first of every month.” Fixed schedules cluster load and ignore the CA’s advice. Check ARI often and renew inside the suggested window.
- “Give every web server full DNS API access.” Delegate
_acme-challengeto a small zone with a narrowly scoped key.
Why it matters to the business
- Leader
Check yourself
You need a certificate for *.example.com. Which ACME challenge can you use?
RightNot quite The answer is B: DNS-01
- What happened
- Wildcard certificates can only be validated with DNS-01.
- Why
- A wildcard covers names that do not exist yet, so the CA checks control of the DNS zone itself, through a TXT record at _acme-challenge.
- The rule
- Wildcards need DNS-01. Delegate _acme-challenge to keep DNS credentials narrow.
Show answer
Answer B DNS-01
- What happened
- Wildcard certificates can only be validated with DNS-01.
- Why
- A wildcard covers names that do not exist yet, so the CA checks control of the DNS zone itself, through a TXT record at _acme-challenge.
- The rule
- Wildcards need DNS-01. Delegate _acme-challenge to keep DNS credentials narrow.
What does ACME Renewal Information (ARI) give an ACME client?
RightNot quite The answer is A: A window of time in which the CA suggests renewing
- What happened
- ARI returns a suggested renewal window for each certificate.
- Why
- The CA can spread renewals out, or ask for early renewal before a mass revocation.
- The rule
- Check ARI regularly and renew at a random time inside the window.
Show answer
Answer A A window of time in which the CA suggests renewing
- What happened
- ARI returns a suggested renewal window for each certificate.
- Why
- The CA can spread renewals out, or ask for early renewal before a mass revocation.
- The rule
- Check ARI regularly and renew at a random time inside the window.
You changed your firewall and want to know whether certbot can still renew. What is the safe check?
RightNot quite The answer is B: Run certbot renew --dry-run
- What happened
- A dry run performs the full exchange against the staging server without saving anything.
- Why
- It exercises the challenge path you just changed, without touching production certificates or rate limits.
- The rule
- Test every change to DNS, firewalls or web servers with a dry run.
Show answer
Answer B Run certbot renew --dry-run
- What happened
- A dry run performs the full exchange against the staging server without saving anything.
- Why
- It exercises the challenge path you just changed, without touching production certificates or rate limits.
- The rule
- Test every change to DNS, firewalls or web servers with a dry run.
Go deeper
- RFC 8555: the ACME protocol, including HTTP-01 and DNS-01.
- RFC 8737: the TLS-ALPN-01 challenge.
- RFC 9773: ACME Renewal Information (ARI).
- Let’s Encrypt: challenge types: the trade-offs of each challenge, and DNS delegation.
- Let’s Encrypt: rate limits and profiles: current limits and certificate profiles.
- Let’s Encrypt: integration guide: when to renew and how to build a reliable client.
- certbot documentation and cert-manager: Certificate resources: the two most common clients in detail.
The crash course covers ACME in lesson 9, Running PKI.
Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.