Certbot renewal
Certbot installs a systemd timer or cron job that runs `certbot renew` twice a day, so renewal is automatic from the moment the first certificate is issued and no manual command is normally needed. `certbot renew` is deliberately a no-op until a certificate is within 30 days of expiry, which means a successful run that renews nothing is the expected result rather than a failure. Verify the whole path end to end with `certbot renew --dry-run`, which exercises renewal against the staging environment without consuming rate limits. The most common real failure is not renewal itself but the reload that should follow it: the certificate on disk is new, the running web server still holds the old one in memory, and the fix is a deploy hook in `/etc/letsencrypt/renewal-hooks/deploy/` that reloads the service.
Renewal is already automatic #
Installing Certbot installs a scheduler. On a systemd distribution that is certbot.timer, on a
snap install snap.certbot.renew.timer, and elsewhere /etc/cron.d/certbot. Each runs
certbot renew twice a day at a randomised minute.
systemctl list-timers | grep -i certbot Expected output
Sat 2026-08-22 04:17:33 UTC 8h left certbot.timer certbot.service
certbot renew renews nothing until a certificate is within 30 days of expiry. A run that reports
No renewals were attempted is working correctly.
Check what is actually managed #
sudo certbot certificates Expected output
Certificate Name: example.com Domains: example.com www.example.com Expiry Date: 2026-11-04 09:12:44+00:00 (VALID: 76 days) Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
Confirm independently what the running server is serving, which is not always the same thing:
openssl x509 -in certificate.crt -noout -enddate openssl x509 -in certificate.crt -noout -checkend 2592000 Expected output
Certificate will not expire
That exits non-zero when the certificate will expire inside the window, which makes it usable directly as a monitoring check.
Test the whole path without spending anything #
sudo certbot renew --dry-run This performs an actual challenge, so it catches a firewalled port 80, expired DNS credentials, a webroot that moved and a plugin that vanished in a distribution upgrade — the four things that break renewal months after issuance. It consumes no rate limit.
The failure that is not a renewal failure #
The single most common report is “Certbot renewed the certificate but the site still serves the old one”. Nothing is wrong with renewal. Web servers read certificates into memory at startup, and a new file on disk is invisible until the process reloads.
Certbot runs every executable in the deploy-hook directory only when a certificate was actually renewed, which is exactly the right trigger:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh When renewal genuinely fails #
Read the log first; it names the challenge that failed and why:
sudo tail -50 /var/log/letsencrypt/letsencrypt.log | Symptom in the log | Cause | Fix |
|---|---|---|
Timeout during connect on HTTP-01 |
Port 80 closed or firewalled | Open port 80; the challenge needs it even on an HTTPS-only site |
404 fetching the challenge file |
Webroot moved, or an alias intercepts /.well-known/ |
Correct webroot_path in the renewal config |
NXDOMAIN / DNS-01 failures |
DNS API credentials expired or rotated | Renew the credentials file named in the renewal config |
unauthorized after a working year |
The domain no longer resolves to this host | Point DNS back, or remove the lineage |
too many certificates already issued |
Duplicate-certificate rate limit | Wait; stop using --force-renewal |
Could not choose appropriate plugin |
Authenticator plugin removed by a distro upgrade | Reinstall the plugin package |
The renewal configuration is a plain file, and editing it is the supported way to change how a lineage renews:
sudo cat /etc/letsencrypt/renewal/example.com.conf Point the server at the right file #
Certbot writes four files per lineage. Which one you reference matters:
| File | Contents | Use it for |
|---|---|---|
fullchain.pem |
Leaf plus intermediates | nginx ssl_certificate, Apache SSLCertificateFile |
cert.pem |
Leaf only | Almost nothing — produces an incomplete chain |
chain.pem |
Intermediates only | Legacy SSLCertificateChainFile on Apache < 2.4.8 |
privkey.pem |
The private key | ssl_certificate_key |
Serving cert.pem is the other half of this trap: the site loads in a browser, which repairs the
missing intermediates itself, and fails for curl, mobile apps and monitoring with
unable to get local issuer certificate.
Wildcards renew differently #
A wildcard lineage can only be validated over DNS-01, so its renewal depends on DNS API credentials
that no one thinks about until they expire. The renewal config records which plugin and credentials
file to use, and --dry-run is the only way to find out that they have stopped working before the
certificate does. See wildcard certificates for why the challenge
type is forced.
Frequently asked questions
- certbot renew says "No renewals were attempted". Is that a failure?
- No, it is the normal result. Certbot only renews inside the last 30 days of a 90-day certificate, so most runs correctly do nothing. Use
certbot renew --dry-runto exercise the path regardless of expiry. - The certificate renewed but my site still serves the old one. Why?
- nginx, Apache and HAProxy read certificates into memory at startup. A new file on disk changes nothing until the service reloads. Add a deploy hook that reloads it — that step is missing far more often than renewal itself fails.
- Do I need a cron job? Every guide adds one.
- Almost certainly not. Package and snap installations both ship a timer that already runs renewal twice a day, and adding a second scheduler means two processes competing for the same lock. Check with
systemctl list-timersbefore writing one. - Should nginx point at cert.pem or fullchain.pem?
fullchain.pem. It contains the certificate followed by the intermediates;cert.pemis the leaf alone, and serving it produces an incomplete chain that browsers hide and every other client rejects.- Can I copy the files out of /etc/letsencrypt/live/?
- Point at them, do not copy them. The files there are symlinks into
archive/that Certbot repoints on each renewal, so a copy is a snapshot that silently goes stale while the symlink stays current. - What does --force-renewal cost?
- It issues a new certificate regardless of expiry, which counts against the duplicate-certificate rate limit. Use it to recover from a genuinely broken certificate, not as a way to test renewal —
--dry-runexists for that and is free.
References
- Certbot user guide — renewal, hooks and the renewal configuration file
- Let's Encrypt rate limits — the current published limits, including the duplicate-certificate limit
- RFC 8555: ACME — the protocol Certbot speaks, and what a challenge actually proves