How to check SSL certificate expiry

Pipe the served certificate into openssl x509 -noout -enddate and read the notAfter line, which is the expiry instant in GMT. For a pass or fail answer inside a pipeline, use openssl x509 -noout -checkend 2592000, which prints a verdict and exits 1 when the certificate dies inside the next 30 days.

Checker offline. Follow the manual steps below, they give the same answer.

Why check this

A certificate that runs out takes every client down at the same second, and it fails before the first byte of HTTP, so the service returns nothing an API client can parse. Run this on staging sign-off, after any infrastructure change that moves a hostname to a new load balancer, and as a scheduled job against production. Renewal automation fails quietly, and the first visible symptom is the outage itself.

Prerequisites

Steps

  1. Step 1.

    Read the validity window off the live connection.

    openssl s_client -connect www.cloudflare.com:443 -servername www.cloudflare.com </dev/null 2>/dev/null | openssl x509 -noout -dates
    
    notBefore=Sep  3 07:35:04 2026 GMT
    notAfter=Dec  2 07:35:03 2026 GMT

    </dev/null matters. Without it s_client keeps the connection open waiting for you to type, and the command never returns.

  2. Step 2.

    Convert notAfter into days remaining, for every hostname you care about.

    for host in www.cloudflare.com example.com expired.badssl.com; do
      end=$(openssl s_client -connect "$host:443" -servername "$host" </dev/null 2>/dev/null \
            | openssl x509 -noout -enddate | cut -d= -f2)
      days=$(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 ))
      printf '%-22s %5d days\n' "$host" "$days"
    done
    
    www.cloudflare.com        81 days
    example.com               46 days
    expired.badssl.com     -4169 days
  3. Step 3.

    Turn the number into an exit code so a pipeline can stop on it.

    openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -checkend 2592000; echo "exit=$?"
    
    Certificate will not expire
    exit=0

    2592000 is 30 days in seconds. Raise it until the command fails, and the first failing value brackets the real headroom.

  4. Step 4.

    Confirm the failing shape against a host that is known to be dead.

    openssl s_client -connect expired.badssl.com:443 -servername expired.badssl.com </dev/null 2>/dev/null | openssl x509 -noout -dates -checkend 0
    
    notBefore=Apr  9 00:00:00 2015 GMT
    notAfter=Apr 12 23:59:59 2015 GMT
    Certificate will expire

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | notAfter more than 30 days out | Normal state for a 90-day certificate | Nothing. Record the date in the release ticket. | | Certificate will expire from -checkend | Expiry falls inside the window you passed | Check the renewal job ran, not only that it is configured. | | A negative day count | The certificate is already invalid | Treat as a production outage. Clients fail closed, no retry helps. | | notBefore in the future | The certificate is not valid yet | A clock skew or an early deploy. Compare with date -u on the server. |

Common mistakes

Sign: The monitoring dashboard is green while users cannot connect.Cause: The monitor pins the certificate it fetched at start-up, or checks the origin behind the CDN rather than the edge hostname the browser reaches. Read the certificate from the same hostname and port a client uses.
Sign: A browser shows a warning page but the tester reports it as a generic connection problem.Cause: Chrome refuses the navigation with net::ERR_CERT_DATE_INVALID and never renders a page, so the interstitial text says only that the connection is not private. The error code names the exact failure, so quote the code in the bug report.
Sign: Expiry looks fine for the apex domain and fails for a subdomain.Cause: Different hostnames on the same IP can be served different certificates through SNI. Without -servername openssl asks for the default virtual host and reads a certificate no user ever sees.

Thresholds

Alarm at 30 days remaining Source: Measured on 2026-09-11 with openssl 3.1.1: total validity was 89 days for www.cloudflare.com, 90 days for example.com and 89 days for api.github.com. A 30-day alarm fires with a third of that lifetime still on the clock.

What to check next

FAQ

How to check SSL certificate expiration date?

Run step 1 and read notAfter. The value is UTC, printed in the openssl format Dec 2 07:35:03 2026 GMT. Your browser shows the same instant in local time, so the calendar day can differ by one without anything being wrong.

How to check certificate expiration date with openssl?

openssl s_client fetches it, openssl x509 -noout -enddate prints it. Keep them as two processes joined by a pipe. s_client alone prints the certificate plus a session dump, and x509 alone reads a file rather than a server.

How to determine SSL certificate expiration date from a PEM file?

Skip s_client and point x509 at the file: openssl x509 -in cert.pem -noout -enddate. The same flag works on a bundle, but it reads only the first certificate in the file, which is the leaf.

Does -checkend cover the intermediate certificates too?

No. It reads one certificate, the leaf. An intermediate that expires first breaks the same connection, so add a chain check rather than trusting a green leaf.

Verified

Verified by Maks Vernyopenssl 3.1.1

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

basic5 minpublished updated Maks Verny