How to monitor SSL certificate expiration
Expired certificates cause outages, browser warnings, and lost trust. Here is how to monitor SSL certificate expiration reliably — without building a homegrown cron job.
▸ The Gap Between Issuance and Deployment
The most common SSL monitoring mistake is watching only the certificate issuance event in Certificate Transparency logs. That tells you when a cert was issued — not whether it was actually deployed. If your Let's Encrypt auto-renewal ran but your web server still serves the old certificate because of a configuration error or a cached proxy layer, CT logs show 'all good' while your users get a browser warning. To monitor SSL certificate expiration correctly, you must probe the actual TLS handshake on port 443 and read the X.509 certificate your server presents in real time.
▸ Essential TLS Fields to Track
The fields that matter are notAfter (the hard expiry date), the issuer chain, the Subject Alternative Names (SAN) list, and the TLS version negotiated. notAfter drives your alerts. The SAN list tells you whether the cert actually covers the hostname — a wildcard cert may cover *.example.com but not example.com itself, causing a mismatch. TLS 1.2 vs 1.3 matters for compliance audits. Reading all of this requires a genuine TLS handshake, not an HTTP status check.
▸ Designing Staged, Idempotent Alerts
Set alert thresholds at 30, 14, 7 and 1 days before expiry. The 30-day mark gives you time to coordinate a renewal through a change management process. The 7-day and 1-day alerts are the safety net: if the renewal was scheduled but silent, those alerts force a manual check. Idempotent alerting matters — you want one notification per threshold crossing, not one per check cycle. Flooding on-call with hourly reminders at T-3 days defeats the purpose of staged thresholds entirely.
▸ Leverage Certificate Transparency (CT) Logs
Certificate Transparency logs add a second layer of visibility. Every publicly trusted CA must submit issued certificates to public CT logs within 24 hours of issuance. You can watch these logs for any new certificate issued for your domain names. This matters for security: if a mis-issued certificate appears for your domain — due to a compromised CA or a social-engineering attack — you will see it in CT before attackers can exploit it, giving you time to request revocation.
▸ Combining Live Probing and CT Monitoring
The combination of live TLS probing and CT monitoring closes both gaps. Live probing catches the 'renewed but not deployed' case. CT monitoring catches mis-issuance and gives you an early signal that a renewal is incoming, before the deployment reaches port 443. Running this on a schedule across all your domains — not just the main hostname but API subdomains, staging environments and internal services — turns certificate management from a reactive scramble into a predictable operation with full audit history.
▸ Multi-Channel Incident Workflows
Alerting channels should match your team's workflow. Email covers the basics and is always the default. Slack channels let you route cert alerts to the infrastructure team without polluting general channels. Webhooks integrate with PagerDuty, Opsgenie, or any custom handler — useful when cert expiry should trigger the same incident workflow as a server outage. SMS is available for highest-urgency scenarios on paid plans. The entry paid tier at $15/mo includes signed webhooks and REST API access; the free tier covers email and one Slack integration.
▸ Rolling Your Own: The Minimum Viable Monitor
If you want to start with a script, this is the honest minimum. It sweeps a list of hostnames and exits non-zero when any of them falls inside the warning window, so cron mails you the output or your CI fails:
#!/usr/bin/env bash
# ssl-check.sh — warn when any host expires within N days
DAYS=${DAYS:-30}
SECS=$((DAYS * 86400))
STATUS=0
for h in "$@"; do
end=$(echo | openssl s_client -servername "$h" -connect "$h:443" 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$end" ]; then
printf '%-30s UNREACHABLE\n' "$h"; STATUS=1; continue
fi
if echo | openssl s_client -servername "$h" -connect "$h:443" 2>/dev/null \
| openssl x509 -noout -checkend "$SECS" >/dev/null; then
printf '%-30s OK (%s)\n' "$h" "$end"
else
printf '%-30s EXPIRING (%s)\n' "$h" "$end"; STATUS=1
fi
done
exit $STATUS Run it as ./ssl-check.sh example.com api.example.com, or from cron with 0 7 * * * /usr/local/bin/ssl-check.sh example.com api.example.com. That is genuinely useful and takes ten minutes to write.
▸ Where the Homegrown Script Runs Out
The script above is a starting point, not a monitoring system. What it does not do is the part that costs time: it has no state, so it cannot tell you what changed; it re-alerts on every run, so it either spams you daily for a month or gets filtered into a folder nobody reads; it has no history, so when an auditor asks when a certificate changed you have nothing; it dies silently if the cron host is down, which is precisely when you need it; and it cannot see Certificate Transparency, so a mis-issued certificate for your domain goes unnoticed.
| CAPABILITY | CRON + OPENSSL | HOSTED MONITORING |
|---|---|---|
| Read live expiry | Yes | Yes |
| Staged 30/14/7/1 alerts | Needs state you must build | Built in |
| One alert per threshold | Re-fires every run | Idempotent |
| Change history / audit trail | None | Retained per host |
| CT log new-issuance alerts | Separate streaming API to implement | Included |
| Fails when the checker itself is down | Silently | Redundant probes |
| Slack / webhook / SMS routing | Glue code per channel | Configured per resource |
▸ Don't Forget What Isn't a Certificate
Two adjacent expiries take sites down just as reliably and are not covered by any SSL check. The first is domain registration: if the domain lapses at the registrar, a perfectly valid certificate is worthless because DNS stops resolving. That is domain expiry monitoring, tracked over RDAP rather than TLS. The second is DNS records: an A or MX record changing without your knowledge is both an outage and a security signal, which is what DNS monitoring watches. Certificate, domain and DNS expiry are three separate clocks, and teams that only watch the first still get paged.
▸ Zero-Maintenance Certificate Lifecycle Monitoring
CertFleet handles the full loop: live TLS probe, CT log cross-reference, staged idempotent alerts, multi-channel delivery, and history. Try the free instant checker to see what a real probe returns for any domain, then add your domains for continuous monitoring — free for up to 10 SSL certificates and 10 uptime monitors, no credit card needed. See how to check a certificate by hand, why SSL monitoring matters, or how continuous monitoring works.
▸ Frequently asked questions
How often should I check SSL certificate expiration?
What alert thresholds should I use for certificate expiry?
Can I monitor SSL certificate expiration with a cron job?
openssl s_client piped to openssl x509 -noout -checkend gives you an exit code you can alert on. What cron does not give you is idempotent alerting, change history, monitoring of the monitor itself, or Certificate Transparency coverage — those are what you build or buy next. Why did my certificate expire even though auto-renewal succeeded?
What is Certificate Transparency monitoring and do I need it?
Should I monitor internal and staging certificates too?
CertFleet probes the live certificate, watches Certificate Transparency, and alerts you 30/14/7/1 days before expiry. Free for 10 certificates, no card.