SSL Certificate Monitoring: What Breaks and How to Catch It
An expired or misconfigured SSL/TLS certificate doesn't slow your site down; it stops it. Browsers show a full-page warning, API clients refuse to connect, and with HSTS enabled visitors cannot even click through. Certificate problems are also among the most predictable outages there are, because the expiry date is written inside the certificate. This guide covers the ways certificates fail, the exact commands to inspect them, why certificate lifetimes are getting shorter, and how much warning your monitoring should give you.
What can go wrong with a certificate
"The SSL is broken" can mean several different things, and each has a different fix.
| Failure | What users see | Typical cause |
|---|---|---|
| Expired certificate | Browser warning (NET::ERR_CERT_DATE_INVALID in Chrome); API calls fail | Auto-renewal silently broken, manual renewal forgotten, renewed cert never deployed |
| Incomplete chain | Works in some browsers, fails in curl, mobile apps, payment callbacks and other servers | Server configured with the leaf certificate only, without the intermediate |
| Hostname mismatch | Warning (ERR_CERT_COMMON_NAME_INVALID) | Certificate lacks www or a new subdomain; wrong virtual host answering; CDN using a default certificate |
| Revoked certificate | Warning in clients that check revocation | Key compromise, CA mis-issuance, domain ownership change |
| Untrusted issuer | Warning on every client | Self-signed or internal CA certificate left on a public site |
| Weak protocol or cipher | Connection failure in modern clients, or a poor security grade | Old server config still offering TLS 1.0/1.1, or offering only legacy ciphers |
The incomplete chain deserves a special mention because it hides from casual testing. Desktop browsers can often repair a missing intermediate themselves (by fetching it from the URL in the certificate, or from a cache of intermediates they have seen before), so the site looks fine when you open it. Command-line tools, many Android versions, language HTTP libraries and webhook senders do not, so the failure only shows up for some clients. Always configure the full chain: with Let's Encrypt and nginx that means ssl_certificate points to fullchain.pem, not cert.pem.
Checking a certificate with openssl
These commands work on any Linux or macOS machine and test what the server actually serves, which is not always what is on disk.
Expiry dates, subject and issuer of the live certificate:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -dates -subject -issuer
The output includes notBefore and notAfter. The -servername flag matters: it sends SNI, so a server hosting many sites returns the certificate for this name. Without it you may be looking at the default certificate.
Is it expiring soon? -checkend takes seconds and exits non-zero if the certificate expires within that window, which makes it handy in scripts:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -checkend $((14*24*3600)) \
&& echo "OK for 14+ days" || echo "Expires within 14 days"
Chain and hostname verification:
echo | openssl s_client -connect example.com:443 -servername example.com \
-verify_hostname example.com 2>/dev/null | grep -E 'Verify return code|depth='
You want Verify return code: 0 (ok). Common failures are 21 (unable to verify the first certificate), which usually means the intermediate is missing, 10 (certificate has expired), and 62 (hostname mismatch). Add -showcerts to print every certificate the server sends; a healthy server sends the leaf plus at least one intermediate.
Which names does it cover?
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -ext subjectAltName
(The -ext option needs OpenSSL 1.1.1 or newer; on older versions use -text and look for "Subject Alternative Name".) Modern clients ignore the Common Name and only check these SAN entries.
Does it still accept old protocols?
openssl s_client -connect example.com:443 -servername example.com -tls1_1 </dev/null
A handshake failure is what you want. Be aware that recent OpenSSL builds may refuse TLS 1.1 locally regardless of the server, so a failure here proves little on its own; a successful handshake, though, means the server still offers it. In nginx, ssl_protocols TLSv1.2 TLSv1.3; is a sensible baseline.
For a quick human-readable summary, curl -vI https://example.com 2>&1 | grep -iE 'expire|subject|issuer|SSL certificate' prints the dates and the verification result in one go.
Certificate lifetimes are getting shorter
For years the maximum validity for publicly trusted TLS certificates was 398 days, so many teams renewed once a year by hand, with a calendar reminder. That is changing. In 2025 the CA/Browser Forum, the body of certificate authorities and browser makers that sets the rules for public certificates, adopted ballot SC-081, which phases in shorter maximum lifetimes. As adopted, the schedule is roughly:
- from March 15, 2026: certificates valid for at most 200 days;
- from March 15, 2027: at most 100 days;
- from March 15, 2029: at most 47 days.
The ballot also shortens how long a CA can reuse a previous domain validation. The exact dates and figures can be revised by later ballots, so check the CA/Browser Forum's Baseline Requirements or your CA's announcements before planning around them. Let's Encrypt certificates already last 90 days, and Let's Encrypt has announced its own plans to go shorter.
The practical consequence is not subtle: manual renewal stops being viable. When certificates last weeks rather than a year, renewal has to be automated (ACME clients such as certbot, acme.sh, Caddy, or your CDN's or host's managed certificates), and automation fails quietly. A renewal job that broke after a server migration gives no sign until the certificate expires. More renewals per year means more chances for that to happen, which is exactly why external monitoring of the certificate actually being served matters. The companion article on catching Let's Encrypt auto-renewal failures covers the typical breakages.
How much warning do you need?
The right lead time depends on how the certificate is renewed and how long a fix could take.
| Renewal method | When renewal normally happens | Suggested alert threshold |
|---|---|---|
| certbot / Let's Encrypt (90-day certs) | About 30 days before expiry (certbot's default) | 14–21 days: if you get the alert, the automatic renewal has already failed for a week or more |
| Managed by CDN or host | Provider-specific, usually well ahead | 14 days |
| Manually purchased certificate | Whenever someone remembers | 30 days or more, to cover purchase approval, validation and deployment |
| Organization or extended validation certificates | Manual, with paperwork | 30–45 days |
The principle: set the alert after the point where automation should have renewed, but with enough days left to fix it calmly. A warning at 3 days is a fire drill; a warning at 20 days for a certificate that auto-renews at 30 tells you the automation is broken while there is still plenty of time.
Also monitor every hostname that has its own certificate: www and the bare domain, the API subdomain, the shop on a separate server, the status page. They often renew through different mechanisms.
FAQ
Why does my site work in Chrome but curl says "unable to get local issuer certificate"?
Most likely the server isn't sending the intermediate certificate. Browsers can sometimes fetch or cache the missing intermediate; curl and most libraries cannot. Serve the full chain (fullchain.pem for Let's Encrypt).
I renewed the certificate but the site still shows the old one. Why?
The web server loads certificates at startup and keeps them in memory. Reload it (sudo systemctl reload nginx) after renewal, and make sure the config points to the renewed file path. If a CDN or load balancer terminates TLS, the certificate must be updated there as well.
How often should certificates be checked?
Expiry changes slowly, but chain and hostname problems appear the moment someone deploys a bad config. Checking along with your regular uptime checks catches both; a daily check is the bare minimum for expiry alone.
Checklist
- Serve the full chain; verify with
openssl s_client ... -servernameand look forVerify return code: 0 (ok). - Check the SAN list covers every name you serve, including
www. - Disable TLS 1.0 and 1.1; offer TLS 1.2 and 1.3.
- Automate renewal, and reload the web server after each renewal.
- Alert on expiry after the point where automation should have renewed, with at least two weeks left.
- Monitor every hostname with its own certificate, from outside your network.
On every HTTPS site Uptime Monitor watches, TLS handshake and certificate problems fail the check with the ssl_error cause, so an expired certificate, a broken chain or a hostname mismatch reaches you by email, Telegram, Slack, Discord or webhook as a downtime alert. Switch on SSL checking for a site and it also records the issuer and expiry date, highlights the certificate on the dashboard as expiry approaches, and sends a warning when the certificate enters a configurable threshold (7 days by default; raise it to match the table above) and once more on its last day. PDF reports on Plus and Professional also include a security headers summary, including HSTS. If you want to know what to do when an alert does fire, keep the first-15-minutes outage checklist handy. Monitoring one site is free.