Programming languages

Node.js CERT_HAS_EXPIRED: certificate has expired fix

By · Last verified

Error messageError: certificate has expired (code: 'CERT_HAS_EXPIRED')

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:

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 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 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.

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 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.

FAQ

The browser shows the site as valid. Why does Node.js say expired?

Browsers may build a different chain, for example skipping an expired cross-signed intermediate the server still sends, and they use their own up-to-date root store. Node.js may also run on a machine whose clock is wrong. Compare the chain from openssl s_client -showcerts with what the browser shows.

How do I find out which certificate has expired?

Print the dates of every certificate the server sends: openssl s_client -connect host:443 -servername host -showcerts </dev/null and run openssl x509 -noout -subject -enddate on each block. The SSL checker lists the expiry of every certificate in the chain.

How do I stop this from happening again?

Automate renewal with an ACME client or your provider's managed certificates, reload the server after renewal, and monitor expiry from outside. With 200-day maximum validity in 2026, falling to 100 days in 2027 and 47 days in 2029, manual renewal will not keep up.

Sources

  1. Node.js TLS (SSL) documentation
  2. OpenSSL X509_STORE_CTX_get_error (verification error codes)
  3. RFC 5280: Internet X.509 PKI Certificate and CRL Profile