Guide · Certificates

How to check an SSL certificate's expiration date

Checked against sources on 2026-09-25 · 5 min read

Every TLS certificate carries two dates: notBefore, when it becomes valid, and notAfter, when it stops. Checking expiry means reading notAfter from the certificate a server actually sends. Below are the ways we use, from quickest to most scriptable, and the mistakes that make each one quietly give you the wrong answer.

In a browser

Open the site, click the icon at the left of the address bar, open the connection or certificate details, and look for the validity period ("Valid to", "Expires on" or similar). Every major browser has this, a couple of clicks deep, and the exact labels move around between versions.

It is fine for one site. It is not great for anything else: you see one hostname at a time, and browsers can paper over server mistakes. Firefox, for example, preloads intermediate CA certificates, so a server that forgets to send its intermediate can look fine in Firefox and still fail for curl, a mobile app or a payment provider's webhook.

If you want the answer without the clicks, our free SSL checker connects from our server and shows the expiry date and issuer for any hostname.

With openssl

This is the command we reach for first:

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

Run against letsencrypt.org on 25 September 2026, it printed:

notBefore=Sep  4 14:34:32 2026 GMT
notAfter=Dec  3 14:34:31 2026 GMT

The pieces: s_client opens a TLS connection. -servername sets SNI, the name the server uses to decide which certificate to send (RFC 6066 defines it). The echo closes the connection straight away so the command does not sit waiting for input. The second command reads the first certificate in the output, which is the server's own, and -dates prints both dates. The openssl x509 manual lists the rest of the options.

A few variations we use a lot:

  • -subject -issuer shows who the certificate is for and who signed it.
  • -ext subjectAltName lists every hostname the certificate covers.
  • -checkend 2592000 exits non-zero if the certificate expires within the next 30 days (2,592,000 seconds). Handy in a cron job or a CI step.
  • -showcerts on the s_client side prints every certificate the server sent, so you can see whether the intermediate is there.
  • openssl x509 -in cert.pem -noout -enddate reads a certificate file on disk instead of a live server.

With curl

curl -vI https://example.com prints the TLS handshake details before the response headers. On builds that use OpenSSL, which is common on Linux, those details include start date, expire date, subject and issuer lines. We checked curl's OpenSSL code to be sure the lines are there.

The curl that ships with Windows uses Schannel, and in our test it printed none of those lines. The curl bundled with Git for Windows did the same. On Windows, use openssl from Git Bash, or PowerShell.

With PowerShell

No extra tools needed. This opens a TLS connection and reads the certificate with .NET:

$h = "example.com"
$tcp = [System.Net.Sockets.TcpClient]::new($h, 443)
$ssl = [System.Net.Security.SslStream]::new($tcp.GetStream())
$ssl.AuthenticateAsClient($h)
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($ssl.RemoteCertificate)
$cert | Select-Object Subject, Issuer, NotBefore, NotAfter
$ssl.Dispose(); $tcp.Dispose()

NotAfter comes back in local time, as Microsoft's documentation says, so convert before comparing it with a UTC date from somewhere else. AuthenticateAsClient also validates the certificate. Against an expired certificate it throws an error ending in NotTimeValid instead of printing dates, which is itself a useful answer.

Checking many hosts

For a handful of hosts, a loop over a text file with one hostname per line is enough. The timeout stops one unreachable host from hanging the whole run:

while read -r host; do
  end=$(echo | timeout 10 openssl s_client -connect "$host:443" -servername "$host" 2>/dev/null \
    | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
  echo "$host  ${end:-no certificate}"
done < hosts.txt

Past a few dozen hosts you will want sorting by days left, retries, and a record of what changed since last week. Our bulk SSL checker checks a pasted list of hostnames in one go. To find hostnames you did not know about, the subdomain certificate finder searches certificate transparency logs, the public record of issued certificates.

Days left, not dates

A date like Dec 3 14:34:31 2026 GMT is easy to misread at a glance. For alerts and reports, days left is the number people actually act on. With GNU date (Linux, Git Bash) you can work it out in one line:

host=example.com
end=$(echo | openssl s_client -connect "$host:443" -servername "$host" 2>/dev/null \
  | openssl x509 -noout -enddate | cut -d= -f2)
echo "$(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 )) days left"

The BSD date on macOS uses different flags, so on a Mac either install GNU coreutils or use -checkend with a number of seconds instead. -checkend is also the safer choice in scripts, because it answers yes or no through the exit code and never has to parse a date.

How often should you check? It depends on the lifetime. A 90-day certificate that renews with 30 days left gives a weekly check four chances to catch a failure. A 47-day certificate gives you about two weeks of slack, so check daily, and alert on "has not renewed yet" rather than waiting for a low days-left number.

What the fields mean

FieldWhat it tells you
notBeforeThe start of validity. A date in the future usually means a clock is wrong somewhere.
notAfterThe expiry. After this moment, clients reject the certificate.
Subject Alternative Name (SAN)The hostnames the certificate covers. If the name you are checking is not in this list, the certificate is wrong for that host, whatever its dates say.
IssuerWho signed it. Let's Encrypt only issues through ACME, so a Let's Encrypt certificate means some automation exists. Whether it still runs is another question.
ChainThe intermediate certificates the server sends along with its own. A missing intermediate breaks clients that cannot fill the gap themselves.

Common gotchas

  • Forgetting SNI. Without the right server name, a shared host may hand back its default certificate, which belongs to some other site. OpenSSL has sent SNI by default since 1.1.1, using the -connect host, but only when that host looks like a DNS name. Connect by IP address and you must pass -servername yourself.
  • Checking the wrong host. example.com and www.example.com can sit on different servers with different certificates. So can mail., shop. and the staging box. Check every name that people or systems actually use.
  • Checking the edge, not the origin. Behind a CDN or load balancer, visitors see the edge certificate. If the CDN verifies your origin, an expired origin certificate breaks the site while the edge certificate looks fine. Check the origin with -connect <origin-ip>:443 -servername example.com.
  • Missing intermediate. Run s_client with -showcerts and count the certificates. Add -verify_return_error and it stops with an error on a chain it cannot verify, instead of carrying on.
  • Time zones. openssl prints GMT. PowerShell prints local time. Mixing the two is how a certificate "expires" an afternoon early.
  • Disk versus served. A certificate file on disk can be renewed while the web server keeps the old one in memory until it is reloaded. Always check what is served.

Or let something else remember

Checking by hand works right up to the week nobody does. ExpiryOwl checks every certificate every six hours and alerts at 30, 14, 7, 3, 1 and 0 days by email, Slack, Teams, Discord, Telegram or webhook. The free plan covers 5 domains with no card.

If you look after servers for several clients on your own, our page for freelance developers is written for you. And if you are wondering why these dates suddenly matter more, read about 47-day certificates.

Sources

  1. OpenSSL: openssl-x509 manual
  2. OpenSSL: openssl-s_client manual
  3. curl source: lib/vtls/openssl.c
  4. Microsoft Learn: X509Certificate2.NotAfter
  5. Mozilla Security Blog: Preloading intermediate CA certificates into Firefox
  6. RFC 6066, section 3: Server Name Indication

FAQ

Questions

What command shows an SSL certificate's expiry date?

Run: echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate. It prints the notAfter date of the certificate the server sends.

How do I check the expiry of a certificate file on disk?

Run openssl x509 -in cert.pem -noout -enddate. Keep in mind that the file on disk is not necessarily what the server is serving until the server has been reloaded.

Why does openssl show a different certificate than my browser?

Usually SNI or a different server. If you connect by IP address without -servername, or reach a different IP than your browser does, the server can send another certificate.

What do notBefore and notAfter mean?

notBefore is the moment a certificate becomes valid and notAfter is the moment it expires. Clients reject the certificate outside that range.

Start with five domains, free.

The free plan watches 5 domains for one client: certificates, domain registration, DNS and uptime every 15 minutes. No card, no trial clock.