Operations
Automate certificate renewal before lifetimes hit 47 days
Automating certificate renewal means an ACME client obtains a certificate on a schedule, installs it on every server, reloads the service and alerts you when a step fails. It is no longer optional: the maximum lifetime of a public TLS certificate has been 200 days since March 15, 2026 and falls to 47 days on March 15, 2029.
Why manual renewal stops working
Manual renewal stops working because a 47-day certificate, renewed at two-thirds of its lifetime, needs about 11.6 renewals a year. Ballot SC-081v3 sets this schedule for the maximum lifetime of publicly trusted TLS certificates:
| Certificates issued | Maximum lifetime | Renewals per year at two-thirds of lifetime | Domain validation reuse |
|---|---|---|---|
| Before March 15, 2026 | 398 days | about 1.4 | 398 days |
| From March 15, 2026 | 200 days | about 2.7 | 200 days |
| From March 15, 2027 | 100 days | about 5.5 | 100 days |
| From March 15, 2029 | 47 days | about 11.6 | 10 days |
The last column matters as much as the first. By 2029 a CA can reuse a domain validation for only 10 days, so nearly every renewal will need a fresh challenge. A process where someone emails a file to a colleague who uploads it to the web root cannot keep up.
Let's Encrypt is moving faster than the rules require. Its opt-in tlsserver profile has issued 45-day certificates since May 13, 2026, its default classic profile switches from 90 to 64 days on February 10, 2027 and to 45 days on February 16, 2028, and its shortlived profile issues 160-hour certificates today. The certificate validity timeline tracks all of these dates, and 47-day certificates explains why the industry chose this path.
The four parts of a renewal pipeline
A renewal is only finished when clients see the new certificate. Most outages come from the steps after issuance, not from issuance itself.
+-----------+ +-----------+ +-----------+ +-----------+
| Obtain | --> | Deploy | --> | Reload | --> | Verify |
| ACME order| | copy to | | nginx, | | external |
| + CSR | | every node| | HAProxy, | | check of |
| | | LB, CDN | | Java app | | served |
+-----------+ +-----------+ +-----------+ | cert |
^ +-----+-----+
| failure alert |
+-----------------------------------------------------+
- Obtain: an ACME client completes a challenge and downloads the new chain.
- Deploy: the certificate and key reach every place that terminates TLS: other web nodes, load balancers, CDNs, mail servers, appliances.
- Reload: each process loads the new files. nginx and HAProxy need a reload; many Java and Node.js applications need a restart or a keystore reload.
- Verify: something outside your network connects and checks the expiry date and chain actually being served.
When to renew: ARI first, one third as a backstop
The best time to renew is the one the CA suggests. ACME Renewal Information (RFC 9773) lets the CA publish a renewal window per certificate and move it earlier when it plans a mass revocation. Let's Encrypt's integration guide gives three rules:
- Check ARI for each certificate at least twice a day and renew inside the suggested window.
- If ARI is unavailable, renew when one third of the lifetime remains, or halfway through for certificates shorter than 10 days.
- Run at a random time of day, not on the hour, and back off after failures: retry after 1 minute, 10 minutes, 100 minutes, then once a day.
For a 90-day certificate the backstop means renewing 30 days before expiry; for a 45-day certificate it means about 15 days before. Let's Encrypt's announcements warn that renewing on a hard-coded 60-day interval will no longer work and suggest searching cron jobs and runbooks for numbers like 60, 80 or 83.
Certbot: timers and deploy hooks
Certbot installs a systemd timer or cron entry that runs certbot renew twice a day on most distributions. Since Certbot 4.0 it treats a certificate as due when less than one third of its lifetime remains; before 4.0 the threshold was a fixed 30 days, which would never trigger in time for short certificates.
# Is a timer installed?
systemctl list-timers | grep certbot
# Rehearse every renewal against the staging CA
sudo certbot renew --dry-run
# Reload nginx only after a successful renewal
sudo certbot renew --deploy-hook "systemctl reload nginx"
A deploy hook runs only when a certificate was actually renewed, unlike --pre-hook and --post-hook, which run around every attempt. You can also drop executable scripts into /etc/letsencrypt/renewal-hooks/deploy/ so that the hook applies to every certificate without editing renewal configs. Certbot sets RENEWED_LINEAGE and RENEWED_DOMAINS for deploy hooks, which is enough to copy files to other hosts.
acme.sh: cron and install-cert
acme.sh installs a daily cron job and keeps its own copy of each certificate. The important step is --install-cert, which records where to copy the files and which command to run after every renewal:
acme.sh --issue -d example.com -d www.example.com -w /var/www/html
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/example.com.key \
--fullchain-file /etc/nginx/ssl/example.com.fullchain.pem \
--reloadcmd "systemctl reload nginx"
Never point nginx at the files inside acme.sh's working directory; the internal layout can change between versions.
Servers with built-in ACME
The simplest pipeline is the one where the server renews its own certificate. Caddy and Traefik obtain and renew certificates by default, Apache httpd has mod_md, and nginx gained an official ngx_http_acme_module in August 2025:
resolver 127.0.0.1:53; # the module needs a resolver for the ACME server
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
state_path /var/cache/nginx/acme-letsencrypt;
accept_terms_of_service;
}
acme_shared_zone zone=ngx_acme_shared:1M;
server {
listen 443 ssl;
server_name example.com;
acme_certificate letsencrypt;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
ssl_certificate_cache max=2;
}
server {
listen 80; # http-01 challenges are answered here
location / { return 404; } # the module answers challenge requests itself
}
Built-in ACME removes the deploy and reload steps for single servers, but you still need external monitoring.
dns-01 automation and credential scope
dns-01 is the right choice for wildcards, internal hosts and fleets, but it puts a DNS credential inside your pipeline. Reduce the blast radius in three ways:
- Use an API token limited to TXT records in one zone if your DNS provider supports scoped tokens.
- Delegate validation with a CNAME, such as
_acme-challenge.example.com CNAME example.com.acme.example.net., so the credential only controls a throwaway zone. - Run issuance on a dedicated host and push the resulting certificates to web servers, instead of storing DNS credentials on every web server.
Many servers and load balancers
With several servers, decide which machine owns issuance. Letting every node run its own client causes duplicate orders, rate-limit errors, and http-01 failures when the CA's request lands on a node that does not hold the token. Common patterns:
| Pattern | How it works | Watch out for |
|---|---|---|
| Central issuer | One host renews with dns-01 and pushes certificates via Ansible, rsync or a secret store | Push failures; key distribution |
| Shared challenge path | Load balancer routes /.well-known/acme-challenge/ to one node |
Rule must survive config changes |
| Terminate at the edge | The load balancer or CDN obtains certificates itself | Origin certificates still need renewal |
| Kubernetes | cert-manager stores certificates in Secrets used by Ingress | Ingress controller must reload Secrets |
Kubernetes with cert-manager
cert-manager is the standard way to automate certificates in Kubernetes. A Certificate resource names the hosts and an Issuer; cert-manager renews it at two thirds of the issued lifetime by default and writes the result into a Secret.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com
spec:
secretName: example-com-tls
dnsNames: ["example.com", "www.example.com"]
issuerRef:
name: letsencrypt
kind: ClusterIssuer
renewBeforePercentage: 33
Prefer renewBeforePercentage over a fixed renewBefore: when the CA shortens its lifetimes, a fixed duration can exceed the new lifetime and cause renewal loops, which the cert-manager documentation warns about.
Monitoring and alerting
Monitoring is what turns a silent renewal failure into a ticket instead of an outage. Let's Encrypt ended its expiration emails on June 4, 2025, citing the cost and the privacy burden of keeping millions of addresses, so you need your own checks.
- Check from outside. Connect to each public name and port as a client would, including SNI. A file on disk tells you nothing about what a load balancer serves. Use the SSL checker for one host or the bulk SSL checker for a list.
- Alert on remaining fraction, not fixed days. Warn when less than one third of the lifetime remains and page when less than one sixth remains. For a 47-day certificate a fixed 30-day warning would fire almost immediately after issuance.
- Alert on the renewal job itself. Export the exit status or last-success timestamp of the client.
- Watch Certificate Transparency. CT logs show every certificate issued for your names, which catches both forgotten hosts and unexpected issuance; see Certificate Transparency.
Rehearse the pipeline before it matters
A renewal pipeline that has never been exercised end to end is a guess. Rehearse it while the current certificate still has plenty of time left:
- Run the client against the CA's staging environment, for example
certbot renew --dry-run, after every change to firewalls, load balancer rules or DNS credentials. - Force one real renewal on a low-risk name and confirm that the deploy hook ran, every node reloaded and your external check saw the new serial number.
- Use a short-lived certificate as a canary. A 6-day certificate on a test hostname exercises the whole pipeline about twice a week, so a broken step shows up within days instead of months.
- Break something on purpose, such as stopping the renewal timer on the canary host, and confirm the alert reaches a human.
Troubleshooting checklist
Most renewal failures come from a short list of causes. Check these in order:
| Symptom | Likely cause | Fix |
|---|---|---|
| Timeout during connect | Port 80 blocked by firewall or cloud security group | Open port 80 or switch to dns-01; see certbot timeout |
| 404 on the challenge URL | Request reached a different server or a redirect broke the path | Route /.well-known/acme-challenge/ to the issuing host |
| CAA error | CAA record does not list your CA | Update the record; see CAA records |
| Rate limited | Repeated failures or duplicate orders | Fix the cause on staging first; see rate limits |
| New certificate on disk, old one served | Service not reloaded, or another node not updated | Add a deploy hook; check every node |
| Chain errors after renewal | Deployed the leaf without the intermediate | Deploy fullchain.pem; see missing intermediate |
| Private key does not match | Key and certificate from different runs | Deploy both files together; see key mismatch |
When you need to inspect what a server presents, the connection testing cookbook and troubleshooting cookbook list the exact OpenSSL commands.
FAQ
How often should my renewal job run?
At least twice a day, at a random minute. The job should be cheap when nothing is due, so frequent runs cost little, and they let an ARI-aware client react within hours when the CA moves a renewal window forward.
Is a renewal cron job enough?
No. Renewal can succeed while the service keeps serving the old certificate because nobody reloaded it, or because another node behind the load balancer never received the file. Add a deploy hook that reloads the service and an external check that alerts when the served certificate has less than about one third of its lifetime left.
What renewal threshold works for 47-day certificates?
Renew at about two thirds of the lifetime, which is roughly 15 to 16 days before expiry for a 47-day certificate, and let ARI move that earlier when the CA asks. Hard-coded values such as 30 days before expiry or every 60 days will fail.
Can I automate renewal for certificates bought from a commercial CA?
Yes, if the CA offers ACME. Most large commercial CAs provide an ACME endpoint with External Account Binding (EAB) credentials, so the same clients and hooks work. Otherwise use the CA's own API or a certificate lifecycle tool.
Sources
- CA/Browser Forum Ballot SC081v3: reducing validity and data reuse periods
- Let's Encrypt: Integration Guide (When to Renew)
- Let's Encrypt: Decreasing Certificate Lifetimes to 45 Days
- Let's Encrypt: Ending Support for Expiration Notification Emails
- Certbot User Guide: renewing certificates
- cert-manager: Certificate resource
- nginx: ngx_http_acme_module