Command-line tools

OpenSSL verify error 10: certificate has expired

By · Last verified

Error messageerror 10 at 0 depth lookup: certificate has expired

Error 10 means a certificate in the chain had already expired at the moment OpenSSL checked it. Look at the depth: at depth 0 the server's own certificate needs renewing; at depth 1 or higher the server is still sending an outdated intermediate; and if everything looks valid, the clock on the checking machine is probably wrong.

Quick fix

The depth of the expired certificate decides the fix.

Print the validity dates of every certificate the server sends, and the current UTC time:

date -u
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
  | awk '/BEGIN CERT/{c++} {print > "cert" c ".pem"}'
for f in cert*.pem; do openssl x509 -in "$f" -noout -subject -enddate 2>/dev/null; done
  • Leaf expired: renew and deploy, then reload the web server.
  • Intermediate expired: rebuild the chain file from the CA's current intermediates.
  • Nothing expired: fix the clock on the machine where the error appears.

Causes

The depth of the expired certificate separates the causes: depth 0 is the server certificate, depth 1 or higher an outdated intermediate.

1. The server certificate expired (depth 0)

  • Confirm: the first certificate's notAfter is in the past.
  • Fix: renew with your ACME client or CA, deploy the new certificate with its chain, and reload. If a renewed certificate exists on disk but the old one is still served, the server was not reloaded or reads another path.

2. An expired intermediate in the bundle (depth ≥ 1)

  • Confirm: the leaf is valid; another certificate in the chain has an earlier notAfter.
  • Fix: replace the CA bundle; never mix intermediates from different years without checking dates. The SSL checker flags expired chain certificates.

3. Old path building with cross-signed roots

Old OpenSSL versions (1.0.2 and earlier) may build a path through an expired cross-signed certificate even when a valid path through a trusted root exists. OpenSSL 1.1.0 and later prefer trusted certificates first.

  • Confirm: the error appears only on old systems; current systems verify the same server.
  • Fix: upgrade OpenSSL, and on the server stop sending the expired cross-sign.

4. Wrong clock on the client

  • Confirm: date -u differs from real time; error 10 or 9 appears for many unrelated sites.
  • Fix: enable NTP (timedatectl set-ntp true, chrony). Virtual machines restored from snapshots and devices without a real-time clock battery are the usual suspects.

5. Error 9: not yet valid

The sibling error 9 (certificate is not yet valid) means notBefore lies in the future. A freshly issued certificate checked by a machine whose clock runs a few minutes behind can trigger it, as can a device whose clock reset to a default date.

  • Fix: correct the client clock; CAs commonly backdate notBefore slightly to absorb small skews, so large errors point to the client.

6. Verifying an old file

Testing a certificate file you saved months ago reports error 10.

  • Fix: verify what the server sends now with s_client, not a local copy.

Fixes by environment

Renewal fixes depth 0, a current bundle fixes depth 1 and above, and time sync fixes the clock. Node.js reports the same failure as CERT_HAS_EXPIRED.

Certbot / acme.sh: run certbot renew --dry-run or acme.sh --cron manually and read the output; make sure a reload hook exists.

Kubernetes: decode the TLS Secret with kubectl get secret <name> -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -enddate.

Java keystores and appliances: certificates imported by hand do not renew; list them with keytool -list -v and replace before expiry.

Monitoring: alert when any certificate in the chain has fewer than 14 days left. With public certificate validity capped at 200 days from 15 March 2026, renewals happen at least twice a year.

Don't do this

  • Use -no_check_time or equivalent application flags on live traffic. You will accept certificates the CA no longer stands behind.
  • Set the clock back to make an expired certificate pass. Everything else time-based breaks.
  • Renew only the leaf and keep an old bundle. Check the intermediates too; the CA may have rotated them.

FAQ

How do I check whether a certificate will be valid on a given date?

Use -attime with a Unix timestamp: openssl verify -attime $(date -d 2026-12-01 +%s) -untrusted chain.pem leaf.pem (GNU date). For a quick expiry check, openssl x509 -checkend 1209600 -noout -in leaf.pem exits non-zero if the certificate expires within 14 days.

The leaf is valid but error 10 appears at depth 1. Why?

The server sends an intermediate that has expired, typically left over from an old chain file. Replace the bundle with the CA's current intermediates; the CA's site or the chain builder provides them.

Can I tell OpenSSL to ignore the date?

-no_check_time exists for diagnosing archives or test data, and applications can set equivalent flags. Do not use it for live connections: expired certificates are no longer covered by revocation services, so a compromised key may go unnoticed.

Sources

  1. OpenSSL openssl-verify
  2. OpenSSL X509_STORE_CTX_get_error (verification error codes)
  3. OpenSSL openssl-verification-options (-attime)
  4. RFC 5280 section 4.1.2.5: Validity