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 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 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 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 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 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 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
-servernameyou 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 -dis GNU-only. On macOS you needdate -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
- Measure API latency with curl
- Check if a port is open on a remote server
- Network triage in plain English
Stop hand-rolling openssl one-liners — describe what you need and CliAI writes the command. Install it in one line.