C
// GUIDE

How to check an SSL certificate

Checking an SSL certificate takes under a minute. What matters is the expiry date, the hostnames it covers, whether the chain is complete — and whether the certificate you are reading is the one your server is actually serving.

There are four ways to check an SSL certificate, and they answer different questions. The browser padlock tells you whether your client trusts the certificate. The openssl command line gives you every field, including the chain. A one-line script gives you an exit code you can put in CI. And an online checker gives you an outside-in view from a network that is not yours. This guide covers all four, then explains the one mistake that makes most certificate checks lie to you.

▸ Method 1 — Check in the browser (30 seconds)

In Chrome and Edge: click the padlock (or the 'Not secure' warning) in the address bar → Connection is secureCertificate is valid. In Firefox: click the padlock → Connection secureMore informationView Certificate, which opens a full-page view with the SAN list already expanded. In Safari: click the padlock → Show Certificate → expand Details.

This shows the issuer, the subject, and the validity window — enough to answer 'is it expired?'. It will not reliably show you the full chain as served, and it reflects your own machine's trust store, which may include corporate root CAs that public clients do not have. A certificate that looks valid on a managed work laptop can still fail for your users.

▸ Method 2 — Check with openssl (the complete view)

The full certificate, every field:

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

The -servername flag sets the SNI hostname. Without it, a server hosting several virtual hosts on one IP returns its default certificate, not yours — which is a classic way to spend an hour debugging the wrong certificate. Always pass it.

Just the expiry dates

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

This prints notBefore and notAfter — the exact validity window the server is serving right now.

Just the hostnames it covers

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

The full chain

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null

A complete chain has the leaf certificate (your hostname), one or more intermediate CA certificates, and terminates at a root the client already trusts. The line that matters is at the end of the output: Verify return code: 0 (ok). Anything else — most commonly unable to get local issuer certificate — means an intermediate is missing. Desktop browsers often paper over this by caching intermediates from other sites, which is why a missing intermediate typically surfaces first as a failure in curl, in a mobile app, or in a server-to-server API call, not in your browser.

▸ Method 3 — Check without openssl

curl is available almost everywhere and prints the certificate during the handshake:

curl -vI https://example.com 2>&1 | grep -E 'subject:|issuer:|expire|SSL certificate'

PowerShell on Windows, no extra tooling:

$r = [Net.HttpWebRequest]::Create('https://example.com')
$r.GetResponse() | Out-Null
$r.ServicePoint.Certificate.GetExpirationDateString()

Python, when you want the parsed fields as data:

import ssl, socket
ctx = ssl.create_default_context()
with ctx.wrap_socket(socket.socket(), server_hostname='example.com') as s:
    s.connect(('example.com', 443))
    cert = s.getpeercert()
print(cert['notAfter'])

▸ Method 4 — A check that fails your build

openssl x509 -checkend takes a number of seconds and sets the exit code: 0 if the certificate is still valid that far ahead, 1 if it will have expired. That makes it a one-line CI gate. Thirty days is 2592000 seconds:

echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -checkend 2592000 \
  && echo 'OK — more than 30 days left' \
  || echo 'WARNING — expires within 30 days'

To sweep several hostnames at once — and you should, because subdomains rarely share a renewal schedule:

for h in example.com www.example.com api.example.com staging.example.com; do
  printf '%-28s ' "$h"
  echo | openssl s_client -servername "$h" -connect "$h:443" 2>/dev/null \
    | openssl x509 -noout -enddate 2>/dev/null || echo 'UNREACHABLE'
done

▸ What the fields actually mean

FIELDWHAT IT TELLS YOUWHY IT BREAKS
notAfterHard expiry date and timePast it, every client refuses the connection outright
subjectAltNameHostnames the certificate is valid forA wildcard *.example.com does not cover example.com itself
IssuerThe CA that signed itAn unexpected issuer can mean a mis-issued or intercepted certificate
Chain / Verify return codeWhether a trust path to a root existsMissing intermediate: works in browsers, fails in curl and on mobile
TLS versionProtocol negotiated (1.2 / 1.3)TLS 1.0 and 1.1 fail PCI-DSS and modern client defaults
Key type & signatureRSA vs ECDSA, SHA-256SHA-1 signatures are rejected by all current clients

▸ The mistake that makes most checks lie

There is a difference between the certificate that was issued and the certificate that is being served. Tools that read Certificate Transparency logs or a CA dashboard tell you what was issued. That is not what your users hit.

The single most common SSL outage is not a forgotten renewal — it is a renewal that succeeded while the web server was never reloaded, so it keeps serving the old certificate from memory until it expires. Your ACME client logs success. Your CA dashboard shows a fresh certificate valid for 90 days. Certificate Transparency shows the new issuance. And port 443 still hands out the expired one. Every method in this guide avoids that trap because they all open a real TLS handshake and read what the server actually presents. Any checker that does not connect to port 443 cannot see this failure.

▸ When manual checking stops working

Manual checks are the right tool for a one-off — debugging a deploy, verifying a migration, answering 'is it me or is it them?'. They stop working as a strategy for a simple reason: they depend on someone remembering. Certificates now last 90 days by default with Let's Encrypt, and the CA/Browser Forum has voted to shorten maximum lifetimes further through 2029. The number of renewals you have to not-forget is going up, not down.

Certificate expiry monitoring replaces memory with a schedule: a probe opens the same handshake you just ran, on every hostname you own, and fires staged alerts at 30, 14, 7 and 1 day before expiry. The free SSL checker here runs a real handshake with no login if you just want the answer now — or monitor up to 10 certificates and 10 uptime endpoints free, no card. See also how to monitor SSL certificate expiration.

▸ Frequently asked questions

How do I check an SSL certificate expiry date from the command line?
Run echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates. It prints notBefore and notAfter for the certificate the server is currently serving. The -servername flag is required on hosts serving multiple sites from one IP address.
Why does my certificate look valid in the browser but fail in curl?
Almost always a missing intermediate certificate. Browsers cache intermediates they have seen on other sites and silently fill the gap, while curl, mobile apps and server-to-server calls do not. Run openssl s_client -connect example.com:443 -servername example.com -showcerts and check for Verify return code: 0 (ok). Fix it by configuring your server to send the full chain file rather than the leaf certificate alone.
What is the difference between checking Certificate Transparency logs and checking the server?
CT logs record what a CA issued. A TLS handshake reveals what your server is actually serving. They diverge in the most common failure mode: the renewal succeeded and appears in CT, but the web server was never reloaded and keeps serving the old certificate until it expires. Only a live handshake on port 443 detects that.
How can I check SSL expiry in a CI pipeline?
Use the exit code of openssl x509 -checkend <seconds>. For example echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -checkend 2592000 exits 0 when more than 30 days remain and 1 otherwise, so it fails the build on its own.
Does a wildcard certificate cover the root domain?
No. A certificate for *.example.com covers one label of subdomains such as www.example.com and api.example.com, but not example.com itself, and not deeper names like a.b.example.com. The root domain must be listed separately in the Subject Alternative Name list. Check with openssl x509 -noout -ext subjectAltName.
How often should SSL certificates be checked?
Daily at minimum. With 90-day Let's Encrypt certificates and maximum lifetimes shortening across the industry, a weekly check can leave a broken renewal undetected for six days. Automated monitoring typically probes every hour and alerts at 30, 14, 7 and 1 day before expiry.
▸ START MONITORING

CertFleet probes the live certificate, watches Certificate Transparency, and alerts you 30/14/7/1 days before expiry. Free for 10 certificates, no card.

Built in France by a developer for real operational needs. Read our architecture, team story and full RGPD details →