- 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.
Related: DEPTH_ZERO_SELF_SIGNED_CERT
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
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
- Who do we trust? CAs, trust stores and self-signed certificates: why each runtime has its own store.
- Related: npm UNABLE_TO_GET_ISSUER_CERT_LOCALLY, curl: self-signed certificate in certificate chain.
Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.