CLI AI

Check SSL certificate expiration with openssl

2026-06-27

The fastest way to check when an SSL certificate expires is echo | openssl s_client -servername host -connect host:443 2>/dev/null | openssl x509 -noout -enddate, which prints the exact notAfter date, no browser required. From there, turn that date into a day count, wire up a pass/fail check for cron, or walk a whole domain list in one pass — the full workflow, not just the one command everyone already knows.

1. When does a site's certificate expire

clai
$ clai when does cliai.tech's certificate expire→ echo | openssl s_client -servername cliai.tech -connect cliai.tech:443 2>/dev/null | openssl x509 -noout -enddate -subjectnotAfter=Nov  2 10:19:50 2026 GMTsubject=CN=cliai.tech

echo | closes stdin so s_client doesn't hang waiting for input. -servername sets SNI and is mandatory whenever one IP serves more than one site — almost always.

2. How many days are left

clai
$ clai how many days until cliai.tech's certificate expires→ exp=$(echo | openssl s_client -servername cliai.tech -connect cliai.tech:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2); echo $(( ($(date -d "$exp" +%s) - $(date +%s)) / 86400 )) days

date -d parses OpenSSL's date format, then it's plain arithmetic in seconds — the number you compare against a threshold in monitoring.

3. A check without doing the arithmetic

clai
$ clai check if the certificate expires in the next 30 days→ echo | openssl s_client -servername cliai.tech -connect cliai.tech:443 2>/dev/null | openssl x509 -noout -checkend 2592000 && echo "ok" || echo "expires within 30 days"

-checkend takes seconds and returns an exit code, not text. 2592000 is 30 days. Ready to drop into a script — nothing to parse.

4. A whole list of domains at once

clai
$ clai check certificate expiry for a list of domains from a file→ while read d; do printf '%-28s ' "$d"; echo | openssl s_client -servername "$d" -connect "$d":443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2; done < domains.txt

Same trick, looped. Keep domains.txt next to the script — updating the list is easier than editing the command.

5. A local certificate file

clai
$ clai show the expiration of this certificate file→ openssl x509 -in /etc/ssl/certs/example.pem -noout -dates

A file already on disk needs no s_client. -dates prints both notBefore and notAfter — the first matters when a certificate is issued "for the future" and isn't valid yet.

6. See the whole chain

clai
$ clai show this site's certificate chain→ echo | openssl s_client -servername cliai.tech -connect cliai.tech:443 -showcerts 2>/dev/null | grep -E 's:|i:'

-showcerts prints the whole chain; s: lines are the subject, i: the issuer. An incomplete chain is a common reason a site loads fine in a browser but fails in curl or a mobile app.

Gotchas

  • Without -servername you might check the wrong certificate. On a shared IP, a server with no SNI hands back its default certificate, and you end up measuring some other site's expiry.
  • date -d is GNU-only. On macOS you need date -j -f '%b %d %T %Y %Z', with the input format spelled out by hand.
  • Certificate expiry and domain expiry are different events. Auto-renewing the certificate won't save you if the domain itself lapses or the ACME challenge breaks.

Related questions

How do I check a certificate on a non-standard port? Swap the port into -connect, e.g. -connect mail.example.com:993, and add -starttls imap for protocols that upgrade the connection.

Why does the browser show a valid certificate while curl complains? Usually an incomplete chain: the browser fetches the missing intermediate itself, curl doesn't. Check the -showcerts output.

How do I automate the alert? Put the -checkend version in cron and notify on a non-zero exit code. A 30-day threshold leaves room to react.

See also

Stop hand-rolling openssl one-liners — describe what you need and CliAI writes the command. Install it in one line.