Back to blog
ssl

How to monitor SSL certificate expiry (openssl one-liners, a cron script, and the proper fix)

Expired TLS certificates still take down real sites in 2026. Here is the full toolkit: openssl one-liners to check any cert now, a cron script that emails before expiry, the failure modes that make DIY checks lie to you, and how to get continuous monitoring for free.

Author

The AlertKick team

4 min read
How to monitor SSL certificate expiry (openssl one-liners, a cron script, and the proper fix)

To check an SSL certificate’s expiry date right now, run echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate, or paste the domain into the free SSL checker for the countdown plus chain and hostname checks. To monitor continuously, either cron that one-liner with an alert threshold (script below) or use a monitoring service that watches the certificate daily and pages you at 21 and 7 days out.

Certificate expiry is the most preventable outage in the industry, and it keeps happening to teams of every size - not because renewal is hard, but because renewal automation fails silently and nothing was watching the certificate itself. The cert that matters is the one a stranger’s browser receives, and checking that is cheap. Here is every level of the toolkit, from a one-liner to done-forever.

Check a certificate right now

The openssl incantation worth memorising (or aliasing):

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -enddate -subject -issuer
notAfter=Oct 12 09:30:00 2026 GMT
subject=CN=example.com
issuer=C=US, O=Let's Encrypt, CN=R13

Three details that save debugging later:

  • -servername matters. It sets SNI. Without it, a server hosting several sites may hand you a different certificate than browsers get - the classic way a manual check says “fine” while users see an expired cert.
  • Check the port you actually serve. The same host’s 443, 8443, SMTP (-starttls smtp -connect mail.example.com:587), and database ports can all carry different certificates on different renewal schedules. Mail and Postgres certs are where expiry hides, because no browser ever warns anyone.
  • Days-remaining as an exit code, which is the building block for scripting - -checkend takes seconds and returns non-zero if the cert expires within them:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -checkend $((14*86400)) \
  || echo "EXPIRES WITHIN 14 DAYS"

If you would rather not remember any of this, the free SSL certificate checker runs the full inspection - expiry countdown, chain trust, hostname match, protocol, SANs - from a browser.

The DIY monitor: a cron script

The honest minimum viable monitor - a script, a domain list, a daily cron:

#!/bin/bash
# /usr/local/bin/check-certs.sh
DOMAINS="example.com www.example.com api.example.com mail.example.com:587"
WARN_DAYS=14

for entry in $DOMAINS; do
  host="${entry%%:*}"; port="${entry##*:}"; [ "$host" = "$port" ] && port=443
  if ! echo | timeout 10 openssl s_client -connect "$host:$port" \
       -servername "$host" 2>/dev/null \
       | openssl x509 -noout -checkend $((WARN_DAYS*86400)) >/dev/null 2>&1; then
    echo "WARNING: cert for $host:$port expires within $WARN_DAYS days (or check failed)"
  fi
done
0 8 * * * root /usr/local/bin/check-certs.sh | mail -E -s "Certificate warnings" [email protected]

This genuinely works, and for a personal project it may be all you need. Run it daily, warn at 14-21 days - enough runway to debug a broken issuance pipeline calmly instead of during an outage.

Where the DIY monitor lies to you

Before trusting the script with anything that matters, know its failure modes - each one is a real pattern, not a hypothetical:

The monitor dies silently. The cron host gets rebuilt, mail breaks, the script errors early - and “no warnings” looks identical to “all certificates fine”. A monitor whose failure mode is silence needs a heartbeat on the monitor itself, at which point you are building a monitoring stack.

It checks from the wrong place. From inside your network, the script may reach an origin server directly and see a fresh certificate, while the load balancer, CDN edge, or reverse proxy - the thing the public connects to - still serves the old one. Renewed-but-not-deployed is the modern expiry outage, precisely because auto-renewal has made forgot-to-renew rare.

Expiry is not the only way a certificate dies. A chain that drops its intermediate after a manual copy, a renewal that issues for the apex but loses a SAN, a hostname mismatch after an infrastructure move - all produce browser errors with a perfectly valid notAfter. The one-liner checks one field; browsers check everything.

The list rots. New subdomain, new service, new region - none of them add themselves to DOMAINS. Certificate coverage that depends on a human updating a shell variable decays at the rate the team ships.

The renewal-era rule

Auto-renewal (ACME, 90-day and now shorter lifetimes) did not remove the need for expiry monitoring; it moved the target. Renewal is an automated pipeline, and pipelines break: the timer that stops firing, the webroot path that changes, the CAA record someone adds, the rate limit hit after a redeploy loop, the reload that never happens after renewal succeeds. More frequent renewal means more chances to break, with less slack when it does.

So the rule: automation renews, monitoring verifies. The check must be external (see what the public sees), continuous (catch the pipeline breaking, whenever it breaks), and cover the whole estate (every domain, including the mail server nobody thinks about).

The done-forever version

That external, continuous, whole-estate check is a solved problem, and solving it is free: an AlertKick SSL monitor checks each certificate from the outside on schedule - expiry countdown, chain validity, hostname match - and alerts on a widening schedule as expiry approaches, through the same escalation paths as everything else (email, Slack, Telegram, WhatsApp, phone, on-call rotation). Domain expiry monitoring rides along, because a domain that lapses takes every certificate under it down with it, and registrar expiry is even easier to miss.

SSL and uptime monitors are on the free plan - set one up in about two minutes, keep the openssl one-liners for spot checks, and let the cron script retire with honour.

Frequently asked questions

How do I check when an SSL certificate expires?
From a terminal: echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate. Or paste the domain into alertkick.com/tools/ssl-checker for the expiry countdown plus chain, hostname, and protocol checks with no install.
Why do certificates still expire unnoticed when auto-renewal exists?
Because renewal and deployment are different steps. The renewer runs but the service never reloads; the cert renews on one host but not the load balancer; a CAA record or rate limit silently blocks issuance; the renewal cron itself dies. Monitoring the certificate the public actually sees is the only check that catches all of these.
How often should certificate expiry be checked?
Daily is plenty - certificates do not expire suddenly. What matters more is the alert threshold: first warning at 21 or 14 days leaves time to fix a broken renewal pipeline calmly; alerting at 3 days converts a config bug into an incident.
Do short-lived certificates make expiry monitoring unnecessary?
The opposite. 90-day and shorter lifetimes mean renewal runs far more often, so there are far more chances for the pipeline to break - and much less time between 'renewal broke' and 'site down'. Automation handles renewal; monitoring verifies it worked.
ssl tls certificates monitoring uptime