Monitoring SSL certificate expiry
An expired certificate takes a site down as surely as a crashed server: browsers show a full-page warning, API clients refuse to connect, and mobile apps break. Automatic renewal made this rarer, not impossible. Renewal jobs fail quietly, and certificates are getting shorter. Here’s how to check expiry, find out why renewal stopped, and get a warning days before it matters.
Why this matters more every year
- The CA/Browser Forum has voted to shorten the maximum lifetime of public TLS certificates in steps: 200 days from March 15, 2026, 100 days from March 15, 2027, and 47 days from March 15, 2029. More renewals mean more chances for one to fail.
- Let’s Encrypt certificates last 90 days, and Let’s Encrypt plans to shorten them too.
- Let’s Encrypt stopped sending expiration reminder emails in June 2025. If you relied on those, nothing warns you anymore.
Check when a certificate expires
From any machine with OpenSSL, for the certificate the site actually serves:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -enddate -issuer -subject
On a server with certbot, for the certificates it manages:
sudo certbot certificates
If the two disagree, certbot renewed the certificate but the web server is still serving the old one.
Why automatic renewal stops
- The renewal job doesn’t run. The certbot timer or cron line was lost when the server was rebuilt, or certbot was installed in a way that doesn’t set one up.
systemctl list-timers | grep certbotshows whether it’s scheduled. - The challenge fails. HTTP-01 needs
/.well-known/acme-challenge/reachable on port 80: a firewall rule, a forced redirect to another host or a CDN in front can break it. DNS-01 breaks when the DNS provider’s API token expires. - Renewed but not reloaded. nginx, Apache or HAProxy keep serving the old certificate from memory until reloaded.
- The certificate lives elsewhere. A copy on a load balancer, a CDN, a mail server or an appliance was uploaded by hand and is never renewed.
- Rate limits. A broken setup that retries too often runs into the CA’s limits, and then even a fixed setup has to wait.
Test renewal without changing anything:
sudo certbot renew --dry-run
Reload the web server after renewal
A deploy hook runs only when a certificate was actually renewed:
sudo certbot renew --deploy-hook "systemctl reload nginx"
To make it permanent, put a script in /etc/letsencrypt/renewal-hooks/deploy/. certbot runs every executable file there after each successful renewal.
Watch the renewal job
certbot exits with 0 when nothing needs renewing yet, so a ping after it means “the job ran and nothing went wrong”. If your system uses the certbot timer, add the ping to its service with sudo systemctl edit certbot.service:
[Service] ExecStartPost=/usr/bin/curl -fsS -m 10 --retry 3 -o /dev/null https://ping.heartcron.com/<uuid>
Or, with plain cron instead of the timer:
17 */12 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx" && curl -fsS -m 10 --retry 3 -o /dev/null https://ping.heartcron.com/<uuid>
Give the check a 12-hour period and a grace period of a few hours. If the job disappears or certbot starts failing, you’ll know weeks before the certificate expires instead of on the day. See monitoring systemd timers for reporting failures right away with OnFailure.
Watch the certificate itself
A job that runs fine doesn’t prove the site serves a fresh certificate: the reload may be missing, or the certificate may come from a CDN or a load balancer you don’t renew yourself. So check from outside too. In Heartcron, add an SSL check for the host and choose how many days ahead to warn, for example 14. Heartcron connects to the site the way a browser does and alerts you when the certificate it serves is about to expire or already invalid.
Add a check for every name that serves its own certificate: example.com, api.example.com, the mail server, a status page on a custom domain. While you’re there, a domain check warns before the domain registration itself runs out, which takes everything down at once.