# OpenSSL verify error 10: certificate has expired

> OpenSSL error 10 means a certificate in the chain was past its notAfter date when checked. Find the depth, then renew, replace the intermediate or fix a clock.

Source: https://ssl.sh/errors/openssl-verify-error-10-certificate-has-expired/
Last updated: 2026-11-10

**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:

```bash
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](https://ssl.sh/tools/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](https://ssl.sh/errors/node-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](https://ssl.sh/timeline/certificate-validity/) 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.
