Certificate Expiration Monitoring: How to Catch SSL Expiry Before Users Do
Last verified on Nov 8, 2026.
An expired certificate is a total outage with a calendar date. Browsers refuse the connection, and for hosts that send an HSTS header they do not offer a click-through, as the Let's Encrypt integration guide points out. API clients fail with a handshake error that your server logs may never record, because the connection dies before any request is made.
It is also the most preventable outage. The expiry date is in the certificate. What fails is the process around it: the renewal job that stopped, the endpoint nobody remembered or the token that someone issued a year ago.
What the evidence looks like
From the outside, the signal is a number of days remaining that keeps falling. This example shows a probe result:
host=api.example.com not_after=2026-11-14T23:59:59Z days_left=6 issuer="Example CA" renewal_expected_at=2026-10-15The last field is the interesting one. If renewal was expected 24 days ago and the certificate still ends in six days, the renewal failed. In your renewal logs, look for the failed attempt:
2026-11-06T03:00:12Z certbot renew: challenge failed for api.example.com: DNS record not foundThis is an example of a renewal log. Your client will word it differently.
How to detect it
- Inventory every certificate: public hostnames, internal services, load balancers, CDN configurations and certificates in a store such as a secrets manager.
- Probe from outside. The Prometheus blackbox exporter exposes the
probe_ssl_earliest_cert_expirygauge, which holds the earliest expiry in the chain as a timestamp.
# Days until the earliest certificate in the chain expires
(probe_ssl_earliest_cert_expiry - time()) / 86400 < 21- For managed certificates, read the platform metric. AWS Certificate Manager publishes
DaysToExpiryin theAWS/CertificateManagernamespace twice a day for every certificate until it expires, so an alarm onDaysToExpiry < 21works per certificate. - For a quick check or a CI step, use openssl. The
-checkendoption exits nonzero if the certificate expires within the given number of seconds:
echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \
| openssl x509 -noout -enddate -checkend $((21*86400))- Extend the same idea to anything with an expiry. A JWT carries an
expclaim that marks the time after which it must not be accepted. Log the expiry of API keys, OAuth grants and signing keys when you issue them, then query for those that end soon.
SELECT credential_id, owner, kind, expires_at
FROM credentials
WHERE expires_at BETWEEN now() AND now() + interval '21 days'
ORDER BY expires_at;Thresholds and pitfalls
Set thresholds relative to your renewal window, not as round numbers. Let's Encrypt recommends renewing when a third of the lifetime is left, which is 30 days before expiry for its 90-day certificates. A warning at 21 days then means a renewal attempt has already failed, and a page at 7 days means you are close to an outage. If you renew later, move the thresholds accordingly.
Lifetimes are shrinking. The CA/Browser Forum ballot SC081v3 reduces the maximum validity of public TLS certificates from 398 days to 47 days, from March 2026 to March 2029. Shorter lifetimes mean any manual renewal step will be missed sooner or later, so check that your thresholds still sit above the renewal interval whenever you change it.
Common mistakes:
- Probing only the main hostname. Other names and CDN edges can serve different certificates, so probe each name users reach.
- Trusting notification email. Let's Encrypt ended its expiration emails on June 4, 2025.
- Checking only the leaf. The earliest expiry in the chain is what breaks clients, which is what the blackbox metric reports.
- Alerting on a metric that disappears. When a probe target is removed or fails to scrape, the alert resolves. Add an absence rule.
- Alert routing. A warning sent to a mailbox nobody reads is the same as no alert. Route it to the owning team.
What to do when it fires
Find out why renewal did not happen before you renew by hand: the logs of the ACME client or the platform show the failed challenge. Fix that cause, trigger a renewal and confirm with the same probe. Deploy the new certificate to every place that holds a copy, including load balancers and sidecars that cache it. For tokens and keys, rotate before expiry and revoke the old ones.
Related guides: stale data covers jobs that stop quietly, which includes renewal jobs, and missed schedules covers cron-style jobs that never ran. For AWS certificates and load balancer logs, see the AWS and Cloudflare pages.
Frequently Asked Questions
How far ahead should I alert on certificate expiry?
Warn at 21 to 30 days and page at 7 days, and make sure those are well above your renewal window. For 90-day certificates, Let's Encrypt recommends renewing 30 days before expiry, so a warning at 21 days means renewal already failed.
Do I still need monitoring if I use automatic renewal?
Yes. Automation fails quietly: a changed DNS record, a blocked challenge or a renewal job that stopped. Let's Encrypt ended its expiration emails on June 4, 2025, so you need your own check.
Should I monitor from inside or outside my network?
Both. An external probe sees the certificate users get, including from a CDN or load balancer. A check on the certificate store sees certificates that no public endpoint serves.
Are certificate lifetimes getting shorter?
Yes. The CA/Browser Forum schedule reduces the maximum validity of public TLS certificates from 398 days to 47 days between March 2026 and March 2029, which makes expiry checks more important every year.
Sources
- Let's Encrypt recommends checking ACME Renewal Information at least twice a day and renewing when a third of the lifetime is left, which is 30 days before expiry for 90-day certificates; browsers do not allow click-through for hosts with HSTS (accessed Nov 8, 2026)
- Let's Encrypt ended expiration notification emails on June 4, 2025 (accessed Nov 8, 2026)
- The DaysToExpiry metric in the AWS/CertificateManager namespace is published twice per day for every certificate until it expires (accessed Nov 8, 2026)
- The Prometheus blackbox exporter defines the probe_ssl_earliest_cert_expiry gauge (accessed Nov 8, 2026)
- CA/Browser Forum ballot SC081v3 reduces public TLS certificate maximum validity from 398 days to 47 days, starting in March 2026 and concluding in March 2029 (accessed Nov 8, 2026)
- openssl x509 -checkend checks whether a certificate expires within a number of seconds and exits nonzero if it will, and -enddate prints the expiry date (accessed Nov 8, 2026)
- The exp claim of a JWT identifies the time on or after which the token must not be accepted (accessed Nov 8, 2026)