Skip to content
ProfitTool
Back to all articles
Privacy & Security
5 min read

How to Check SSL Certificate Expiry: 5 Methods

An expired SSL certificate blocks all users with a hard browser warning. Here are five ways to check certificate expiry before it becomes a problem.

ProfitTool Team

Security & Dev

Why Expiry Checks Are Not Optional Any More

An expired certificate does not degrade a site — it stops it. Browsers show a full-page interstitial, APIs throw TLS errors, and mobile apps that pin certificates fail closed. There is no partial outage and no graceful fallback.

The window for catching this is also shrinking. The CA/Browser Forum voted in 2025 to reduce the maximum lifetime of public TLS certificates in stages: 200 days from March 2026, 100 days from March 2027, and 47 days from March 2029. Annual renewal reminders in a calendar were already fragile; at 47 days they stop being viable at all. Automated issuance plus automated monitoring is the direction every public certificate is moving in.

This guide covers five ways to check expiry, from a one-off manual look to continuous monitoring.

Method 1: The Browser

Fastest for a single site. Click the padlock in the address bar, open the certificate details, and read the validity period.

BrowserPath
Chrome / EdgePadlock → Connection is secure → Certificate is valid
FirefoxPadlock → Connection secure → More information → View Certificate
SafariPadlock → Show Certificate

Two limits worth knowing. The browser shows you the certificate for the hostname you visited, not for every hostname on the server — and it shows the leaf certificate, so an expiring intermediate is easy to miss. It is a spot check, not an audit.

Method 2: OpenSSL

The tool of record. This works against any TLS service, not just HTTPS.

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -dates
notBefore=Jun 12 08:31:04 2026 GMT
notAfter=Dec 29 08:31:03 2026 GMT

-servername is not optional. It sends the SNI extension. Without it, a server hosting multiple sites returns its default certificate — which may be a completely different site's — and you will confidently read the wrong expiry date. This is the most common way an OpenSSL check produces a misleading answer.

Useful variations:

# Every name the certificate covers
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

# Issuer, subject and dates together
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

# A local certificate file
openssl x509 -in /etc/ssl/certs/example.crt -noout -dates

# Non-HTTPS services
openssl s_client -connect mail.example.com:25 -starttls smtp </dev/null 2>/dev/null \
  | openssl x509 -noout -dates

Method 3: A Yes/No Check for Scripts

-checkend takes a number of seconds and sets the exit status: 0 if the certificate is still valid beyond that point, 1 if it expires within it. This is what you want in monitoring, because it needs no date parsing.

# Will it still be valid in 30 days? 30 * 86400 = 2592000
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -checkend 2592000

echo $?   # 0 = fine, 1 = expires within 30 days

A complete check script:

#!/usr/bin/env bash
set -uo pipefail

THRESHOLD_DAYS=${THRESHOLD_DAYS:-30}
SECONDS_AHEAD=$((THRESHOLD_DAYS * 86400))
STATUS=0

for host in "$@"; do
  cert=$(echo | openssl s_client -connect "${host}:443" -servername "$host" 2>/dev/null \
         | openssl x509 2>/dev/null)

  if [ -z "$cert" ]; then
    echo "ERROR    $host — could not retrieve certificate"
    STATUS=1
    continue
  fi

  expiry=$(echo "$cert" | openssl x509 -noout -enddate | cut -d= -f2)

  if echo "$cert" | openssl x509 -noout -checkend "$SECONDS_AHEAD" >/dev/null; then
    echo "OK       $host — expires $expiry"
  else
    echo "EXPIRING $host — expires $expiry (within ${THRESHOLD_DAYS} days)"
    STATUS=1
  fi
done

exit $STATUS
$ ./check-certs.sh example.com api.example.com
OK       example.com — expires Dec 29 08:31:03 2026 GMT
EXPIRING api.example.com — expires Sep 30 11:02:44 2026 GMT (within 30 days)

The non-zero exit makes it usable directly as a cron job, a CI step, or a Nagios-style check.

Portability note. Many published versions of this script convert the date with date -d, which is GNU-specific and fails on macOS and BSD, where the equivalent is date -j -f. Using -checkend avoids date parsing entirely, so the script above runs unchanged on Linux and macOS.

Method 4: Check the Whole Chain, Not Just the Leaf

Your certificate can be perfectly valid while the site still breaks, because an intermediate certificate in the chain has expired. The leaf is what everyone monitors; the intermediate is what nobody does. It has caused several well-publicised outages.

# Expiry date of every certificate the server presents
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
  | awk '/BEGIN CERT/,/END CERT/' \
  | openssl storeutl -noout -text /dev/stdin 2>/dev/null \
  | grep -E "Subject:|Not After"

Also confirm the chain actually verifies, which catches a missing intermediate as well as an expired one:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>&1 \
  | grep -E "Verify return code|verify error"

Verify return code: 0 (ok) is what you want. A missing intermediate often works in desktop Chrome — which can fetch it — while failing in mobile apps and curl, which is why "it works on my machine" is a poor test here.

Method 5: Continuous Monitoring

Manual checks catch what you remember to check. Renewal fails on the host nobody thought about.

ApproachHow it worksSuits
Cron + the script aboveRuns daily, emails or posts on non-zero exitSmall estates, no extra infrastructure
Prometheus blackbox exporterExposes probe_ssl_earliest_cert_expiry; alert on the remaining timeTeams already running Prometheus
Uptime monitoring servicesMost bundle certificate expiry alerts with uptime checksTeams already paying for uptime monitoring
Certificate Transparency feedsWatch CT logs for issuance on your domainsDetecting unexpected or unauthorised issuance

A Prometheus alert rule:

- alert: CertificateExpiringSoon
  expr: (probe_ssl_earliest_cert_expiry - time()) / 86400 < 21
  for: 1h
  labels:
    severity: warning
  annotations:
    summary: "{{ $labels.instance }} certificate expires in {{ $value | printf \"%.0f\" }} days"

Note that probe_ssl_earliest_cert_expiry reports the earliest expiry in the chain, so it covers the intermediate problem from Method 4 automatically.

Choosing an Alert Threshold

The threshold should be set by how long a manual recovery takes, not by habit.

Certificate lifetimeSuggested first alertReasoning
90 days (Let's Encrypt)21 daysCertbot attempts renewal at 30 days remaining; alerting at 21 means several automated attempts have already failed
200 days (current CA/B maximum)30 daysEnough room for a manual reissue and a change window
Internal / private PKI30–45 daysInternal processes are usually slower and less automated

The principle for automated renewal: alert after automation should have succeeded, not before. Alerting at 45 days on a Let's Encrypt certificate fires while renewal is still working normally, and an alert that is usually noise is an alert people stop reading.

Automate Renewal, Then Monitor It

Monitoring tells you renewal failed. Automation is what stops it failing.

# Certbot: what is installed and when it expires
sudo certbot certificates

# Test renewal without hitting rate limits
sudo certbot renew --dry-run

# Is the renewal timer actually running?
systemctl list-timers | grep certbot

Certbot's renew only acts on certificates within 30 days of expiry, so a renew that reports "not yet due" is success, not failure. The --dry-run is the meaningful test.

The failure worth planning for is not renewal itself but deployment: a certificate that renews on disk while the running service keeps serving the old one from memory. Whatever reloads nginx, HAProxy, or your application must run as a deploy hook after every successful renewal, and it must be tested — a renewal that never reaches the listening socket looks fine in certbot certificates and expires in production.

Common Mistakes

  • Omitting -servername. Returns the wrong certificate on any shared or multi-tenant host.
  • Monitoring the CDN, not the origin. If the CDN terminates TLS, its certificate is the one you see. The origin's certificate expires independently and takes the site down just as effectively.
  • Monitoring only the apex. api., admin., staging. and internal service endpoints have their own certificates and their own renewal paths.
  • Relying on the CA's expiry emails. They go to whatever address was on file at issuance — often someone who has left.
  • Assuming automation works because it was set up. Renewal breaks on credential rotation, DNS provider migrations, and firewall changes. --dry-run on a schedule is the only proof.
  • Ignoring non-HTTPS services. SMTP, IMAP, LDAP and database TLS certificates expire on the same clock and are monitored far less often.

Frequently Asked Questions

What happens the moment a certificate expires? Clients reject the connection outright. Browsers show an interstitial the user can sometimes click through; APIs, mobile apps and server-to-server calls generally cannot, so integrations break before anyone reports a website problem.

Can I still check expiry if the site is already down? Yes, if the server still completes a TLS handshake — openssl s_client reports the dates regardless of whether the certificate is valid. If the service is fully down, check the certificate file directly on the host with openssl x509 -in <file> -noout -dates.

Does renewing early waste the remaining time? No. CAs issue a new certificate with a full validity period from the issuance date; you are not charged the unused remainder. Renewing at 30 days remaining loses nothing.

Why does one browser complain when another does not? Almost always a missing or expired intermediate. Browsers differ in whether they fetch the missing certificate themselves, so serving the complete chain from your server is the fix.

Check expiry dates, the full chain, and every covered hostname for any domain with the SSL Certificate Checker. If a wildcard certificate is not covering the host you expected, What Is a Wildcard SSL Certificate? explains the matching rules — and the DNS Lookup tool will confirm an ACME DNS-01 record has propagated before a renewal run.

Tags:#ssl#certificate expiry#https#monitoring#web security

ProfitTool Team

Security & Dev

Written and maintained by the ProfitTool team. Where an article references a ProfitTool utility, the behaviour described is the behaviour of the tool on this site — you can open it and check the output yourself.

Try the Interactive Tools Mentioned

Free to use with no ProfitTool signup. Data handling varies by tool.