• Node.js
  • Exact text checked with the real tool

SELF_SIGNED_CERT_IN_CHAIN

Also seen in: npm, yarn, axios, fetch (undici), Electron

In one sentence

The server sent a complete chain that ends in a root Node.js does not trust, almost always a TLS-inspection proxy's root or a company CA.

Where you see it

In Node.js HTTPS clients and, very often, in npm install on a company network. The code comes from OpenSSL’s verify error 19; Node.js lists these codes in its TLS documentation.

node:events:485
      throw er; // Unhandled 'error' event
      ^

Error: self-signed certificate in certificate chain
    at TLSSocket.onConnectSecure (node:_tls_wrap:1679:34)
    at TLSSocket.emit (node:events:507:28)
    at TLSSocket._finishInit (node:_tls_wrap:1078:8)
    at ssl.onhandshakedone (node:_tls_wrap:864:12)
...
  code: 'SELF_SIGNED_CERT_IN_CHAIN'
}
$ npm install
npm error code SELF_SIGNED_CERT_IN_CHAIN
npm error errno SELF_SIGNED_CERT_IN_CHAIN
npm error request to https://registry.npmjs.org/express failed, reason: self-signed certificate in certificate chain

Older npm versions print npm ERR! instead of npm error.

What it means

Every chain ends in a root certificate, which signs itself. A root is trusted only because it is already in the client’s trust store. Here the server sent the whole chain including a self-signed root, and that root is not among the roots Node.js trusts. Node.js uses its own bundled list of public roots by default, not the operating system’s store, so a root that IT installed on your laptop may be missing for Node.js only.

Likely causes

A TLS-inspection proxy re-signs your traffic

If this happens for registry.npmjs.org or other public sites on a company network, a TLS-inspection proxy or antivirus product is decrypting HTTPS and re-signing it. Check the issuer:

openssl s_client -connect registry.npmjs.org:443 -servername registry.npmjs.org </dev/null 2>/dev/null | openssl x509 -noout -issuer

If it names your company or a security product instead of a public CA, get that root certificate from IT in PEM format and trust it:

export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/corp-root.pem
npm install

NODE_EXTRA_CA_CERTS adds certificates to Node.js’s built-in roots when the process starts, so public sites keep working (Node.js CLI documentation). Set it in your shell profile, CI variables or Dockerfile:

COPY corp-root.pem /usr/local/share/ca-certificates/corp-root.crt
RUN update-ca-certificates
ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root.crt

Node.js can also trust the operating system’s store, where IT usually deploys the proxy root: start it with --use-system-ca (Node.js 22.15 and 23.8 on Windows and macOS, 23.9 on other platforms), or set NODE_USE_SYSTEM_CA=1 (Node.js 22.19 and 24.6).

The server uses a private CA

Internal registries, Git servers and APIs often use a company CA. List what the server sends:

openssl s_client -connect npm.example.com:443 -servername npm.example.com -showcerts </dev/null 2>/dev/null | grep -E '^ [0-9]+ s:|^   i:'

The last entry has the same subject (s:) and issuer (i:): that is the root. Get it from your PKI team through a trusted channel, compare its SHA-256 fingerprint (openssl x509 -in corp-root.pem -noout -fingerprint -sha256), and add it with NODE_EXTRA_CA_CERTS as above.

For npm only, npm config set cafile /path/to/bundle.pem also works, but the file replaces npm’s built-in roots (npm config documentation), so it must contain the public roots as well, or the public registry fails with UNABLE_TO_GET_ISSUER_CERT_LOCALLY. When a ca is set, Node.js also ignores NODE_EXTRA_CA_CERTS for those connections.

Your Node.js is old

Each Node.js release bundles a snapshot of public roots. A very old version may lack a newer public root. Upgrade Node.js to a supported release.

If the code is DEPTH_ZERO_SELF_SIGNED_CERT (message self-signed certificate), the server sent a single self-signed certificate with no CA at all, typical of a development server. Replace it with a certificate from your company CA or a public CA. For a local test, trust exactly that certificate with NODE_EXTRA_CA_CERTS or the ca option.

Check yours

Try it: Chain checkerOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

Every certificate the server sends, leaf first. The output of openssl s_client -showcerts -connect example.com:443 works as it is: we ignore the text around the certificates.

Paste every certificate from openssl s_client -showcerts. The checker marks the self-signed root, explains when a client would trust it, and points out root names that belong to known HTTPS-inspection products.

Learn why

In the gameLevel 12: Who do we trust?Registry of Roots, Keystone. In development

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9