SSL certificate monitoring: the complete guide
SSL certificate monitoring is the practice of continuously checking the certificates that secure your domains so you find problems before your users do. Most teams only think about it after an outage, when a forgotten cert expired at 2 a.m. and every client started rejecting connections. This guide covers what to watch beyond the expiry date, how often to check, how to design useful alerts, and whether to build it yourself or use a dedicated tool.
Why monitor certificates at all
Certificates are time-bombed by design. Public CAs now issue leaf certificates with a maximum lifetime measured in months, and automation occasionally fails silently: a renewal hook doesn't fire, a reload is skipped, a wildcard covers fewer hosts than you assumed. When a cert expires, browsers and API clients hard-fail with errors like CERT_HAS_EXPIRED, and there is no graceful degradation. A single expired cert can take down a checkout flow, an internal API, or a mobile app backend.
The failure is also asymmetric. The cost of monitoring is trivial; the cost of a missed expiry is an outage plus an incident review. That math is why SSL certificate monitoring belongs in the same category as uptime and DNS monitoring, not as a nice-to-have.
What to check beyond the expiry date
Expiry is the headline, but a certificate can be valid and still broken. A complete check looks at several things.
Chain validity
A server must present the leaf certificate and the intermediate(s) that link it to a trusted root. Browsers often paper over a missing intermediate using cached or AIA-fetched certs, but many API clients, mobile platforms, and older runtimes do not. The result is intermittent, hard-to-reproduce TLS failures. Inspect the chain your server actually sends:
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
Look at the Certificate chain block. If it stops at the leaf, you're missing intermediates. For a deeper walkthrough of how leaf, intermediate, and root certs fit together, see the certificate chain explained.
Hostname and SAN match
The certificate's Subject Alternative Name (SAN) list must include the exact hostname clients use. The legacy Common Name is ignored by modern clients. A mismatch surfaces as ERR_TLS_CERT_ALTNAME_INVALID and commonly bites you when adding a new subdomain or moving from example.com to www.example.com. Dump the SANs:
openssl x509 -in cert.pem -noout -ext subjectAltName
Or pull them straight off a live host:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -ext subjectAltName
Protocol version and weak ciphers
Monitoring should flag how the connection is negotiated, not just whether it succeeds. Confirm the host offers TLS 1.2 and 1.3 and rejects deprecated TLS 1.0/1.1. You can probe a specific version directly:
# Should fail on a well-configured server
openssl s_client -connect example.com:443 -tls1_1 </dev/null
# Should succeed
openssl s_client -connect example.com:443 -tls1_3 </dev/null
A non-zero exit on the TLS 1.3 probe is a configuration regression worth alerting on.
OCSP and revocation
A certificate can be revoked before it expires (key compromise, mis-issuance). Check revocation status via OCSP:
openssl ocsp -issuer chain.pem -cert cert.pem \
-url http://ocsp.example-ca.com -no_nonce -text
If you serve OCSP stapling, verify the stapled response is present and current:
openssl s_client -connect example.com:443 -status </dev/null 2>&1 | grep -A 17 "OCSP response"
A missing or stale staple means clients fall back to live OCSP lookups, adding latency and a soft-fail dependency.
Certificate Transparency, briefly
Every publicly trusted certificate is logged to CT logs. Watching CT logs for your domains is less about expiry and more about detecting unexpected issuance — a cert for your domain that you didn't request can indicate a misconfigured CI pipeline or, worse, a compromise. It's a complementary signal, not a replacement for endpoint checks.
How often to check
For expiry, daily is plenty. Certificates are valid for weeks or months, so checking once a day gives you ample lead time without hammering your endpoints. There's no benefit to per-minute polling for expiry dates.
Some checks justify a higher cadence. Chain and hostname problems usually appear at deploy time, so pairing a daily scheduled check with a post-deploy check catches regressions fast. Revocation and OCSP staple freshness sit comfortably in the daily sweep. If you only do one thing, run a daily job. For a focused recipe on the expiry piece, see how to check SSL certificate expiration date.
Alert lead times and channels
A single alert the day a cert expires is useless — by then you're already in an incident. Use multiple thresholds, escalating as the deadline nears:
- 30 days — informational. Renewal should already be automated; this confirms it's on track.
- 14 days — warning. If automation hasn't renewed yet, investigate now.
- 7 days — actionable. Page the owning team.
- 1 day — critical. Treat as an active incident.
Multiple thresholds matter because a single notification is easy to miss, snooze, or lose in a noisy channel. Spacing them out turns one fragile signal into a ramp.
Multiple channels matter for the same reason. An email is fine for the 30-day heads-up; a Slack message reaches the on-call team faster at 7 days; a webhook into PagerDuty or your incident tooling is right for the 1-day critical. Wiring alerts to email and Slack and a webhook means no single broken integration silences the whole pipeline.
Build vs. buy
You can absolutely roll your own. A minimal DIY expiry check in cron looks like this:
#!/usr/bin/env bash
set -euo pipefail
DOMAIN="example.com"
THRESHOLD_DAYS=14
end_date=$(echo | openssl s_client -connect "$DOMAIN:443" -servername "$DOMAIN" 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
end_epoch=$(date -d "$end_date" +%s) # GNU date; use `date -j -f` on macOS
now_epoch=$(date +%s)
days_left=$(( (end_epoch - now_epoch) / 86400 ))
if (( days_left <= THRESHOLD_DAYS )); then
echo "WARNING: $DOMAIN expires in $days_left days" \
| mail -s "Cert expiry: $DOMAIN" oncall@example.com
fi
Schedule it daily:
0 7 * * * /usr/local/bin/check-cert.sh
This works, and for a couple of domains it's reasonable. The hidden costs show up as you scale: handling dozens of hosts and SANs, parsing chains, OCSP, dedup-ing alerts, retries on transient network errors, and the irony of your cron host's own cert or mail relay quietly failing. A dedicated tool exists so you don't maintain monitoring infrastructure that itself needs monitoring. For one-off manual inspection, a hosted SSL check tool is faster than remembering openssl flags.
Whichever path you choose, keep your renewal automation healthy too. Server-specific renewal and reload steps live in the Nginx guide and the AWS guide for ACM and load-balancer certs.
Monitor it automatically
SSLNudge runs these checks for you on a daily schedule — expiry, chain, hostname/SAN match, and TLS configuration — and alerts you at sensible lead times across the channels you use, so a forgotten renewal becomes a notice instead of an outage. Add your domains once and let it watch them. Sign in to get started.
Stop tracking expiry dates by hand
SSLNudge checks your certificates daily and alerts you before they expire.