
How to check SSL certificate expiration
To check an SSL certificate's expiration date, open the site in any browser and click the padlock, or run echo | openssl s_client -connect example.com:443 | openssl x509 -noout -enddate from a terminal.
Both take about ten seconds. The harder question is what happens the other 364 days, and that question keeps getting worse. The maximum life of a public certificate has already dropped to 200 days, and it is heading to 47 by 2029. A check you run by hand once a year used to be sloppy. Soon it is useless.
The fastest ways to check one certificate
If you only need the date for a single host, pick whichever of these is already in front of you.
In a browser
Click the padlock in the address bar, then open the certificate details. Every major browser shows the validity window under a 'Valid from / Valid to' or 'Expires' field. Fine for a one-off, no help for anything you need to repeat.
With openssl
The command-line client gives you the exact dates plus an exit code you can script against. On macOS the bundled client is LibreSSL 3.3.6; the flags below behave the same on OpenSSL.
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
| openssl x509 -noout -enddate
# notAfter=Aug 29 21:41:26 2026 GMTWant both ends of the validity window? Swap -enddate for -dates:
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
| openssl x509 -noout -dates
# notBefore=May 31 21:39:12 2026 GMT
# notAfter=Aug 29 21:41:26 2026 GMTLook at those two dates on example.com itself: issued May 31, expiring August 29. That is a 90-day certificate. Short-lived certs are not a future worry, they already run large parts of the web.
For scripts, skip the date parsing entirely. -checkend takes a number of seconds and sets its exit code: 0 if the certificate is still valid for that long, 1 if it expires sooner.
# exit 0 if valid for at least 30 more days, exit 1 if it expires within 30
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
| openssl x509 -noout -checkend 2592000
echo $? # 0With curl
If curl is already open, it prints the expiry in its verbose handshake output:
curl -sSv https://example.com 2>&1 | grep "expire date"
# * expire date: Aug 29 21:41:26 2026 GMTWith an online checker
Plenty of free web checkers pull the certificate for you. They are fine for a public hostname you do not mind typing into someone else's server. Do not paste internal or pre-release hostnames into one: you are handing a stranger a list of names worth attacking.
Reading the date is easy. Remembering to is not.
The mechanics above are trivial. The failure mode is never "I could not read the date." It is "nobody looked." A certificate expires on a fixed timestamp with no grace period, no soft warning to your visitors, and no retry. The moment it lapses, every browser throws a full-page security error and most people back out. An expired certificate is downtime with a scary warning on top, and downtime has a real cost.
Manual checking is a single point of human failure. It depends on one person remembering, on the right day, for every host, forever. That was a thin bet when certificates lasted a year. It falls apart completely once they last weeks.
Why "check it once a year" is already broken
Here is the part the older guides miss: the ground rules changed.
In 2025 the CA/Browser Forum, the group whose rules browsers enforce for public certificates, voted to cut the maximum certificate lifetime in stages. The schedule is set out in the CA/Browser Forum baseline requirements, and DigiCert has a plain-language breakdown.
| Effective date | Max certificate lifetime | Domain validation reuse |
|---|---|---|
| Before 2026-03-15 | 398 days | 398 days |
| 2026-03-15 (in effect now) | 200 days | 200 days |
| 2027-03-15 | 100 days | 100 days |
| 2029-03-15 | 47 days | 10 days |
We are already past the first step. Since March 2026 a new public certificate cannot be valid for more than 200 days, so even a yearly check now skips at least one full renewal. By 2029 a 47-day certificate renews roughly eight times a year.
The reasoning behind the change is sound: shorter lifetimes shrink the damage window when a key leaks or a certificate is mis-issued, and they push teams onto automated renewal instead of hand-installed certs. The side effect is just as real. Anything you used to do by hand now has to become a job a machine runs on a schedule.
How to stay ahead of expiration
Two separate jobs: renew automatically, and verify independently. Most teams do the first and skip the second.
Automated renewal (Let's Encrypt with an ACME client like certbot, or your platform's built-in TLS) handles issuance. It is not a monitor. ACME clients fail quietly: a DNS change breaks validation, a renewal cron dies, a load balancer keeps serving the old certificate after a successful renew. You find out when the cert has already expired, unless something is watching from outside the system that renews it.
The minimum viable watcher is the -checkend loop on a daily cron:
#!/usr/bin/env bash
# warn if any cert expires within 21 days
THRESHOLD=$((21 * 86400))
for host in example.com api.example.com www.example.com; do
if echo | openssl s_client -servername "$host" -connect "$host:443" 2>/dev/null \
| openssl x509 -noout -checkend "$THRESHOLD" >/dev/null; then
echo "ok $host"
else
end=$(echo | openssl s_client -servername "$host" -connect "$host:443" 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
echo "WARN $host expires $end"
fi
done# /etc/cron.d/ssl-check, runs daily at 08:00
0 8 * * * ops /usr/local/bin/ssl-check.sh | mail -s "cert check" ops@example.comThat script is genuinely useful, and it has a short shelf life. It cannot alert when the box running it is the thing that is down. It keeps no history, so a certificate that flapped leaves no trace. It has no escalation path, so a missed email is a missed outage. It only checks the hostnames you remembered to list, which is never all of them. And it watches the leaf on port 443, not the intermediate that actually expired, or the certificate on your SMTP, your internal admin panel, or a staging box nobody owns.
At that point you want the check living off the box, with history and real alerting behind it. That is the job of dedicated SSL certificate monitoring: it validates the full chain from leaf to root on a schedule, tracks every host from one place, and warns you before expiry instead of after. Build it or buy it, the rule is the same. A human memory is not a monitoring strategy.
What an expired certificate actually breaks
When a certificate lapses, the TLS handshake fails and the browser refuses to connect. Visitors hit a full-page interstitial (NET::ERR_CERT_DATE_INVALID in Chrome) that most will not click past. APIs are worse. A client library reaches the expired endpoint, throws certificate has expired, and the call fails with no friendly page to explain it, often inside a background job nobody is watching until the data stops flowing.
So check the date when you need it, with the browser or openssl. Then assume you will forget, and put something in place that does not. The certificate does not care that you were busy. It expires on the timestamp printed in notAfter, to the second.