Check SSL certificate expiry
Print the notBefore / notAfter dates of a remote site's TLS certificate (or a local .pem file).
How to check ssl certificate expiry in each shell
Bashunix
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -datesZshunix
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -datesFishunix
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -datesPowerShellwindows
$r = [Net.WebRequest]::Create("https://example.com"); $r.AllowAutoRedirect = $false; $r.GetResponse().Close(); $r.ServicePoint.Certificate | Select-Object Subject, Issuer, @{N="NotAfter";E={$_.GetExpirationDateString()}}pwsh 7 alternative without raw .NET: `(Invoke-WebRequest -Uri https://example.com).BaseResponse.RequestMessage.Headers` — but `[Net.WebRequest]` still gives the easiest path to the X509Certificate object on both 5.1 and 7+. For local `.pem` files: `(New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 "cert.pem").NotAfter`.
cmd.exewindows
powershell -NoProfile -Command "$r = [Net.WebRequest]::Create('https://example.com'); $r.AllowAutoRedirect = $false; $r.GetResponse().Close(); $r.ServicePoint.Certificate.GetExpirationDateString()"cmd ships `curl.exe` but `curl` doesn't expose cert dates cleanly. PowerShell shell-out is the practical answer.
Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.
Gotchas & notes
- `-servername` is the SNI hostname — REQUIRED on any modern HTTPS server (every shared-host CDN edge: Cloudflare, Fastly, AWS CloudFront). Without `-servername`, you get the edge's default certificate (often `*.cloudflareedge.com` instead of your actual site), which expires on its own schedule and is almost never what you want to monitor.
- The `echo |` redirect terminates `openssl s_client`'s interactive prompt — without it, the command hangs waiting for input. macOS BSD `openssl` is LibreSSL by default (`/usr/bin/openssl --version` reports `LibreSSL`), not OpenSSL — most flags are compatible but some (`-trace`, `-msg`) differ. `brew install openssl@3` for the GNU one; binary at `/opt/homebrew/opt/openssl@3/bin/openssl`.
- Days-until-expiry one-liner: `echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate | sed 's/notAfter=//' | xargs -I{} date -d "{}" +%s | awk -v now=$(date +%s) '{print int(($1-now)/86400)}'`. macOS `date` doesn't accept `-d`; use `date -j -f "%b %e %T %Y %Z" "$STR" +%s` or do the math in PowerShell.
- For automated monitoring (alerting on expiry < 30 days): `nagios-plugins-network` ships `check_http -C 30` and `check_ssl_cert`. Don't roll your own cron — Let's Encrypt renewal cycles, intermediate-CA rotations, and chained-cert expiries are subtle. Better: pay for/run uptimerobot, Pingdom, or self-hosted Uptime Kuma which handle SNI + chain validation correctly.
Related commands
Related tasks
- Check if a URL is reachable— Test whether a URL returns 2xx/3xx — useful for healthchecks, wait-for-it scripts, and CI smoke tests.
- Get HTTP response headers— Inspect just the response headers of a URL — useful for debugging redirects, caching, CORS, and TLS.
- Follow HTTP redirects— Print the redirect chain (301/302/303/307/308 hops) from a starting URL to its final 2xx destination.