Run openssl x509 with the checkend option and a number of seconds. It exits with zero if the certificate will still be valid after that many seconds and nonzero if it will expire. Wrap it in a cron job against the live endpoint, alert on a nonzero exit, and treat it as a minimum, not a full monitoring system.
What checkend does
The OpenSSL manual describes checkend as checking whether the certificate expires within the next arg seconds, exiting nonzero if it will expire and zero if not. With the multi option it checks every certificate in the input and fails if any will expire in that time. That makes it useful for a bundle file that contains a chain.
Check the certificate the server actually presents
A file on disk can be fine while the server still serves an older certificate from memory. Probe the endpoint instead. The command below fetches the served certificate and tests whether it lasts at least 14 more days (1209600 seconds).
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -checkend 1209600
The servername value matters because a server may choose its certificate by SNI. See wrong certificate served because of SNI and debugging with s_client.
A small cron script
#!/bin/sh
host="$1"; days="${2:-14}"; secs=$((days*86400))
if ! echo | openssl s_client -connect "$host:443" -servername "$host" 2>/dev/null \
| openssl x509 -noout -checkend "$secs" >/dev/null; then
echo "Certificate for $host expires within $days days or could not be read" \
| mail -s "TLS expiry warning: $host" ops@example.com
fi
A failed connection also gives a nonzero exit, which this script reports. That is deliberate: an unreachable or broken endpoint should raise the same alarm.
Choosing thresholds
| Window | Use |
|---|---|
| 21 to 30 days | Early warning for 90 day certificates renewed at about 30 days left |
| 14 days | Warning that renewal has stalled |
| 7 days | Page a person |
| Hours | For six day certificates |
Match the thresholds to your renewal setting. See why Let's Encrypt emails are gone.
Limits of this approach
- It checks expiry only. It does not check the chain, hostname match or revocation.
- It tests one port and one name per call. Loop over every name you serve.
- It runs from one place. A certificate can differ by region or load balancer node, so probe from outside too.
- Cron mail depends on a working mail setup. Send to your real alerting tool.
For wider coverage, use a probe that exposes the expiry as a metric, so you can graph and alert on it with the rest of your monitoring.
Steps
- List the hostnames and ports to test.
- Save the script and run it by hand against a known good and a known expired test endpoint.
- Schedule it in cron to run daily.
- Route failures to your alert channel, not a personal inbox.
- Add a heartbeat so you know the job itself is still running.
Where Safeguard fits
Safeguard does not monitor certificate expiry. It provides continuous monitoring of dependencies, plus SBOM scanning and policies. The demo shows how ongoing checks surface new findings.
Try it yourself
Click through it at your own pace, or open it full screen.
Notice the difference between a one time scan and checks that keep running on a schedule.
Quick check
What does a nonzero exit mean?
The certificate will expire within the number of seconds you passed to checkend.
Can it check a whole chain?
Yes, with the multi option it checks every certificate in the input and fails if any of them will expire in time.
Is this enough for production monitoring?
It is a good floor. Add chain and hostname checks and alerts that go to your on-call system.
Sources
Last reviewed: 19 October 2026.