How do I know a renewal timer exists and will fire?
certbot.timer, which starts certbot.service, which runs certbot renew. The snap version uses different names: snap.certbot.renew.timer and snap.certbot.renew.service. If Certbot was installed with pip into a virtualenv, there is often no timer at all. Plenty of people assume one exists anyway.
Run systemctl list-timers --all and look for a Certbot entry. The NEXT and LEFT columns tell you when it fires next. LAST and PASSED tell you when it last ran. An empty LAST column, or a LAST date from months ago, tells you something before you dig any further. If nothing Certbot-related appears, check /etc/cron.d/ and root's crontab. Some packages ship a cron file as a fallback, and that file is usually written to step aside when systemd is running. Read it rather than guessing which mechanism you rely on.
Also confirm the timer is enabled, not just present. Use systemctl is-enabled certbot.timer, or the snap unit name. A timer that was disabled during some earlier troubleshooting session looks fine at first glance, but it never fires.
The timer ran last night. Does that mean the certificate renewed?
certbot renew often, typically twice a day, but certbot renew only replaces a certificate once it falls inside the renewal window. By default that means within 30 days of expiry. Newer Certbot versions can also take a renewal hint from the certificate authority. On most runs, Certbot looks at each certificate, decides it is not due yet, and exits cleanly. So a successful timer run usually means nothing was renewed, and that is expected behavior.
The consequence is that a broken renewal path can stay hidden for weeks. Certbot reports success on every run because there is nothing to do. The real attempt happens only when the window opens. If the DNS has changed since installation, port 80 is now blocked, or the webroot path moved during a migration, that is the run that fails. To see what Certbot decided, read the service journal with journalctl -u certbot.service. Lines saying a certificate is not yet due for renewal are normal. Anything mentioning a failed challenge or an error deserves attention.
sudo certbot certificates gives you the other half of the picture. It lists each certificate Certbot manages, the domains it covers, the expiry date, and the file paths. If the expiry date is already inside the 30-day window and the certificate still has not renewed, the timer is firing but the renewal itself is failing.
What does certbot renew --dry-run actually prove?
certbot renew --dry-run performs a full renewal for every managed certificate against the Let's Encrypt staging server. Staging is a test environment that issues untrusted certificates and does not count toward the production rate limits. Nothing is saved over your real certificate. That makes it safe to run at any time, and it is the fastest way to answer one question: can Certbot complete the domain validation challenge right now, with the current DNS, firewall, and web server config?
It does not prove everything. In recent Certbot versions, a dry run skips deploy hooks by default unless you add --run-deploy-hooks. Pre and post hooks still run. So if your setup relies on a deploy hook to reload Nginx or restart a mail server, a clean dry run tells you nothing about that hook. It also runs in your shell, as you, rather than inside the systemd service environment. That difference rarely matters, but it can show up when a hook script depends on a PATH or an environment variable that the service does not have.
When the dry run fails, the error usually names the domain and the challenge type. For HTTP validation, the common causes are DNS pointing somewhere else (sometimes only the AAAA record), a redirect or rewrite rule catching /.well-known/acme-challenge/, or a firewall that blocks port 80 because only HTTPS seemed necessary. In support work these failures often trace back to changes made long after Certbot was installed, by someone who had no reason to think about renewal.
Renewal succeeded, so why does the site still serve the old certificate?
/etc/letsencrypt/live/yourdomain/, which are symlinks into the archive directory. Nginx and Apache read those files at startup or on reload, not on every connection. Until the web server reloads, it keeps serving the certificate it loaded earlier, even though a newer one is sitting on disk.
Whether that reload happens depends on how the certificate was originally requested. Open /etc/letsencrypt/renewal/yourdomain.conf and look at the authenticator and installer lines. If the installer is nginx or apache, Certbot handles the reload itself after a successful renewal. If you used --webroot or --standalone with certonly, there is no installer. Certbot renews the files and stops there. Then you need a deploy hook: either a renew_hook line in that renewal config, or an executable script in /etc/letsencrypt/renewal-hooks/deploy/ that reloads your web server. Deploy hooks run only when a certificate was actually renewed, so they do not reload anything on the routine no-op runs.
The same gap applies to anything else that uses the certificate, such as a mail server, an FTP daemon, or a reverse proxy in another container. Each one needs its own reload, and none of them happen on their own.
Where do I read what happened, and how do I confirm the live certificate?
/var/log/letsencrypt/letsencrypt.log holds Certbot's own detailed log, including the challenge requests, the hooks it ran, and their exit status. Older runs are rotated into numbered files in the same directory. When a hook fails, the detail usually shows up here rather than in the short journal summary.
The final check happens from the outside: which certificate does the server actually present to a client? Comparing that expiry date with certbot certificates tells you whether the reload step is working. Here are the checks together. All of them are read-only except the dry run, which is safe because it only talks to staging. Replace example.com with your domain and use the snap unit names if that is how Certbot was installed.
BASH
# Is a renewal timer scheduled, enabled, and has it run recently?
systemctl list-timers --all | grep -i certbot
systemctl is-enabled certbot.timer
# What did recent runs decide?
journalctl -u certbot.service --since '-7 days'
# Expiry dates and paths Certbot knows about
sudo certbot certificates
# Can renewal complete right now? Uses the staging server, saves nothing
sudo certbot renew --dry-run
# Which certificate is the live site actually serving?
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddatecertbot certificates shows a later expiry than the openssl output, renewal is working but the reload is not. That points straight at the installer or deploy hook question above. If both dates match and are recent, the whole chain worked the last time it was needed.
