How to check when an SSL certificate expires

The short answer

Every TLS certificate has a notAfter date. Once that date passes, browsers show a full-page warning. Chrome's version says NET::ERR_CERT_DATE_INVALID. You can read the date in a browser for one site, or with a command to check many sites or to script a check.

1. In the browser (one site, no tools)

The browser shows the certificate your connection received. If the site sits behind a CDN, that is the CDN's edge certificate, which may not be the one installed on the origin server. See the pitfalls section below.

2. With openssl (macOS, Linux, WSL)

`bash

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \

| openssl x509 -noout -enddate

`

Output:

`

notAfter=Mar 3 12:00:00 2027 GMT

`

Notes:

Is it expiring soon? Get a yes/no answer

-checkend takes a number of seconds. It exits with status 1 if the certificate expires within that window:

`bash

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \

| openssl x509 -noout -checkend $((21*86400)) \

&& echo "OK for 21+ days" || echo "Expires within 21 days"

`

That exit code is all a cron job or CI step needs.

3. With curl

`bash

curl -svI https://example.com 2>&1 | grep -i "expire date"

`

This works when curl is built with OpenSSL, which is the usual case on Linux. Some builds, such as Schannel on Windows or Secure Transport on older macOS, don't print the date. If you see nothing, use openssl instead.

4. With PowerShell (Windows, no extra installs)

`powershell

$h = "example.com"

$tcp = New-Object Net.Sockets.TcpClient($h, 443)

$ssl = New-Object Net.Security.SslStream($tcp.GetStream(), $false, { $true })

$ssl.AuthenticateAsClient($h)

$cert = New-Object Security.Cryptography.X509Certificates.X509Certificate2($ssl.RemoteCertificate)

"{0} expires {1} ({2} days)" -f $h, $cert.NotAfter, ($cert.NotAfter - (Get-Date)).Days

$ssl.Dispose(); $tcp.Dispose()

`

The { $true } callback accepts any certificate, so the script can still read the date from one that has already expired.

5. On the server itself

If you have shell access and know where the file is:

`bash

openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -enddate

`

If you use Certbot, sudo certbot certificates lists every certificate it manages with its expiry date and days remaining.

Pitfalls that give you the wrong date

Why checking matters more now

The CA/Browser Forum is shortening the maximum lifetime of public certificates (ballot SC-081). The cap is 200 days for certificates issued from March 15, 2026, 100 days from March 15, 2027, and 47 days from March 15, 2029 (DNSimple summary). Shorter lifetimes mean more renewals, and each renewal is another chance for one to fail without anyone noticing. Let's Encrypt also stopped sending expiration reminder emails in June 2025, so you can no longer count on it to warn you.

If you're checking more than a handful of sites

The commands above work well for one site. For dozens, wrap the openssl command in a loop over a list of domains, sort the results by days left, and schedule it. We're building Lapsewatch for agencies that need exactly this. It will keep one list of every client domain with days-to-expiry and send an email or Slack alert before any certificate lapses. It isn't live yet; you can follow its progress on the page below.

Try it