certificate has expired and ERR_CERT_DATE_INVALID: which certificate, and why.

The error names a date problem but not the certificate. Often it is the leaf, because a renewal failed or was never loaded. Sometimes the leaf is fine and an intermediate in the chain has expired, or the client's clock is wrong. Each has a different fix, and checking takes a minute.

Written by · Published 2026-09-30 · 6 min read

The same error in every client

ClientError
Chrome, EdgeNET::ERR_CERT_DATE_INVALID
FirefoxSEC_ERROR_EXPIRED_CERTIFICATE (or ..._EXPIRED_ISSUER_CERTIFICATE)
curl, OpenSSLSSL certificate problem: certificate has expired, verify error:num=10
PythonCERTIFICATE_VERIFY_FAILED ... certificate has expired
Node.jsCERT_HAS_EXPIRED
Gox509: certificate has expired or is not yet valid
JavaCertificateExpiredException

None of them tells you which certificate in the chain failed. OpenSSL comes closest: openssl s_client prints depth=0 for the leaf, depth=1 and up for intermediates, next to the error.

Find out which certificate expired

Run the host through the SSL certificate chain checker. It lists every certificate the server sent with its dates, and reports the leaf and any expired intermediate as separate findings, so "leaf expired" and "intermediate expired" do not look the same. The SSL/TLS certificate checker gives the leaf's expiry date and days left, which is what you want to monitor.

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
  | awk '/BEGIN CERT/,/END CERT/' | csplit -s -z -f cert- - '/BEGIN CERT/' '{*}'
for c in cert-*; do openssl x509 -in "$c" -noout -subject -enddate; done

The leaf expired

The usual case. Renewal is automated nearly everywhere now, so an expired leaf almost always means the automation broke quietly some weeks ago. Look for the reason in this order:

Maximum certificate lifetimes are shrinking under the CA/Browser Forum's schedule, from 398 days to 200 in March 2026 and on down to 47 days by 2029. Manual renewal is no longer a realistic plan. If a certificate is still renewed by hand, automate it now or monitor it (below).

An intermediate expired, the leaf is fine

The leaf is new, the error persists, and the chain checker shows an old date on certificate two. The server's chain file was assembled years ago and nobody replaced the intermediate when the leaf was renewed. This is common with commercial CAs, where the leaf and the "bundle" arrive as separate downloads. Rebuild the chain from the intermediate that actually signed the current leaf; how to build a correct fullchain.pem has the steps.

A related case: an old root or cross-sign expired, and old clients fail even though a valid path exists. When Let's Encrypt's DST Root CA X3 cross-sign expired in September 2021, clients on OpenSSL 1.0.2 kept choosing the expired path and failed, while everything current chose the new root and worked. If only old clients fail, update the client or its trust store, or serve a chain that does not lead them to the expired certificate.

Nothing expired, and the client still says so

Check the clock on the client. A device whose date is years out (a Raspberry Pi with no real-time clock, a restored VM snapshot, a laptop with a dead CMOS battery) sees every certificate as expired or not yet valid. Go's message includes the time it believes it is, which gives it away. date and turning NTP on fix it.

Monitor it so it does not happen again

Alert on days left, not on the error. A daily job that fails under 14 days gives two weeks to fix a broken renewal:

days=$(curl -sf "https://kirkdiamond.com/tools/tls?host=example.com&format=json" | jq '.chain[0].days_left')
[ "$days" -ge 14 ] || echo "example.com certificate expires in $days days"

More checks like this are in the API and curl reference. Domain Health also warns under 14 days and fails once the certificate has expired.

All guides · All tools