# Node.js CERT_HAS_EXPIRED: certificate has expired fix

> Node.js reports CERT_HAS_EXPIRED when a certificate in the chain is past its notAfter date or the client clock is wrong. Check the server, chain and clock.

Source: https://ssl.sh/errors/node-cert-has-expired/
Last updated: 2026-11-10

**`certificate has expired` (`CERT_HAS_EXPIRED`) means one of the certificates in the chain Node.js built is past its expiry date, as judged by the client's clock.** Usually the server certificate itself was not renewed; sometimes the culprit is an expired intermediate the server still sends, or a client whose date is wrong.

## Quick fix

Either a certificate's `notAfter` date is in the past or the client clock is wrong; the dates below show which.

Check the server's certificate dates and your clock:

```bash
date -u
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -startdate -enddate
```

- **`notAfter` is in the past:** renew the certificate and deploy it with its full chain, then reload the web server.
- **Leaf is valid:** check every certificate the server sends with `-showcerts`; remove expired intermediates from the bundle.
- **Your clock is wrong:** fix time synchronisation on the client (`timedatectl set-ntp true` on systemd Linux).

## Causes

An expired server certificate is the usual cause; an expired intermediate or a wrong client clock is less common.

### 1. The server certificate expired

- **Confirm:** the command above shows `notAfter` earlier than now; the [SSL checker](https://ssl.sh/tools/ssl-checker/) reports it expired.
- **Fix:** renew. For ACME clients run `certbot renew` or `acme.sh --renew -d example.com`, and make sure a deploy hook reloads Nginx or Apache. If renewal "succeeded" but the old certificate is still served, the web server was not reloaded or points to a copy.

### 2. An expired intermediate or old cross-sign in the bundle

Servers sometimes keep sending an intermediate or cross-signed certificate from an old chain after it expired. Clients with a different path-building strategy then fail.

- **Confirm:** `-showcerts` lists a certificate other than the leaf with an expired `notAfter`.
- **Fix:** rebuild the chain file from the CA's current intermediates (the [chain builder](https://ssl.sh/tools/chain-builder/) helps) and drop expired ones.

### 3. Wrong client clock

Containers on hosts without time sync, VMs restored from snapshots and embedded devices often run with a wrong date.

- **Confirm:** `date -u` is far from real time; other HTTPS sites fail with the same error.
- **Fix:** enable NTP (chrony, systemd-timesyncd) on the host; containers use the host clock.

### 4. Outdated Node.js root store or mirror configuration

Old Node.js releases ship root lists that still contain, or rely on, expired roots. Old registry or API hostnames that have been retired may also present expired certificates.

- **Fix:** upgrade to a supported Node.js LTS release and update outdated endpoints in configuration (for npm, check `npm config get registry`).

## Fixes by environment

Renew the certificate wherever it is served, and monitor expiry from outside so a failed renewal does not go unnoticed.

**Certbot:** `sudo certbot renew --deploy-hook "systemctl reload nginx"`; verify the timer with `systemctl list-timers | grep certbot`.

**Kubernetes:** check the TLS Secret's certificate and cert-manager status; see [cert-manager certificate not ready](https://ssl.sh/errors/cert-manager-certificate-not-ready/).

**Managed load balancers and CDNs:** an uploaded certificate does not renew itself; switch to the provider's managed certificates or automate the upload.

**Monitoring:** check expiry from outside the server, because a renewal job that fails quietly is the usual root cause. A daily check that alerts when any certificate in the chain has fewer than 14 days left catches both forgotten renewals and servers that renewed but never reloaded. The [validity calculator](https://ssl.sh/tools/validity-calculator/) shows how long certificates issued today can be valid.

**Not yet valid:** the related code `CERT_NOT_YET_VALID` means the opposite problem: the certificate's `notBefore` is in the future from the client's point of view. That almost always means the client clock is behind, not that the server is wrong.

**Clients you cannot update:** if an old device or runtime cannot validate the current chain, upgrade it; there is no safe way to accept expired certificates.

## Don't do this

- **`NODE_TLS_REJECT_UNAUTHORIZED=0`** to get past an expired certificate. It disables all verification, not only the date check.
- **Set the system clock back.** It breaks other certificates, logs and token validation.
- **Ignore an expiry warning because "only Node.js complains".** Other non-browser clients (mobile apps, partners' integrations) are failing too.
