Error Knowledge Base npm CERT_HAS_EXPIRED

npm ERR! code CERT_HAS_EXPIRED

npm rejected the HTTPS connection because the registry or proxy certificate is expired, so the TLS handshake can no longer be trusted.

Where the Request Failed

npm rejected the HTTPS connection because the registry or proxy certificate is expired, so the TLS handshake can no longer be trusted.

npm is telling you the request failed before it got a clean response back. Treat the connection path and the failing environment as the first suspects, not the package or image name.

Fix certificate trust and TLS

Start by proving the failing machine can reach the right host cleanly. Until DNS, routing, proxy, and trust look sane in that exact environment, retrying the install or pull is mostly noise.

Confirm which registry host npm is calling:npm config get registry

Check system time and timezone (NTP). If it's wrong, fix it and retry.

Check for proxy settings:npm config get proxy and npm config get https-proxy

Check TLS config:npm config get strict-ssl and npm config get cafile

If you control the registry/proxy, renew or rotate the certificate and include intermediates.

Proper fix:trust the CA that is signing the certificate chain.

If you are behind a corporate TLS proxy, export the corporate root CA and configure npm:npm config set cafile /path/to/corp-ca.pem

In CI, prefer NODE_EXTRA_CA_CERTS=/path/to/corp-ca.pem so Node trusts the internal CA without changing global npm config.

If you control the registry/proxy, ensure it serves the full certificate chain (leaf + intermediates).

Inspect the served chain:openssl s_client -showcerts -connect <host>:443 -servername <host> </dev/null

Temporary diagnostics only:if you must confirm a trust issue, run npm --strict-ssl=false <command> once, then restore validation immediately.

Do not leave strict-ssl=false or NODE_TLS_REJECT_UNAUTHORIZED=0 in CI, shell profiles, or checked-in config. They disable certificate validation and increase MITM risk.

If you changed npm config for a one-off test, revert it immediately:npm config set strict-ssl true and unset NODE_TLS_REJECT_UNAUTHORIZED

Retry with npm --verbose and keep the full output for troubleshooting.

Manual certificate validation

If the main log is noisy or truncated, these checks let you isolate the failing layer directly and confirm whether you are dealing with configuration, access, trust, or local environment state.

Confirm the failing host matches your registry:npm config get registry

If you control the registry/proxy, ensure it serves the full certificate chain (leaf + intermediates).

Inspect the served chain:openssl s_client -showcerts -connect <host>:443 -servername <host> </dev/null

Look at the leaf certificate Not After date and confirm it is not expired.

If you must do a one-off diagnostic retry with SSL disabled, revert the change immediately after the test.

Why It Happens

Usually this comes down to system time is wrong (certificate validity checks fail immediately), the registry/proxy certificate is actually expired, or a corporate TLS proxy is serving an outdated certificate chain.

Prove the Failing Environment Can Reach It

Run npm ping (same network, same registry) and confirm it succeeds, and re-run the original command and confirm the TLS error no longer appears.

How npm verifies TLS certificates

npm uses Node.js for HTTPS. Certificate validation happens in Node's TLS stack. Expired certificates are rejected during the TLS handshake before npm can fetch package metadata.

Examples

npm ERR! code CERT_HAS_EXPIRED

Prevent Repeat Connectivity Failures

To prevent this, keep build machines time-synced (NTP), monitor certificate expiry for registries and proxies, and bake corporate root CAs into CI images when TLS interception is expected.

Docs and source code

github.com/npm/cli/blob/417daa72b09c5129e7390cd12743ef31bf3ddb83/lib/utils/ping.js

This is the registry request path where npm talks to the network. DNS/TLS errors like this code are raised by Node/OS during this request. - GitHub

// used by the ping and doctor commands
const npmFetch = require('npm-registry-fetch')
module.exports = async (flatOptions) => {
  const res = await npmFetch('/-/ping', { ...flatOptions, cache: false })
  return res.json().catch(() => ({}))
}

Need help or found a mistake? Contact RepoFlow support for questions.

Join our mailing list