Error message
self-signed certificate in certificate chain
Also printed as
- code: 'SELF_SIGNED_CERT_IN_CHAIN'
- SELF_SIGNED_CERT_IN_CHAIN
Node.js 16 and older (OpenSSL 1.1.1) spelled it "self signed certificate in certificate chain". With fetch() it is the cause of "TypeError: fetch failed"; npm prints it as "npm error code SELF_SIGNED_CERT_IN_CHAIN".
Node.js tls / https / fetch — code SELF_SIGNED_CERT_IN_CHAIN (OpenSSL X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN, 19)
What it means
The chain the server sent ends in a self-signed root certificate that is not among the roots Node.js trusts. On a company network this is the signature of a TLS-inspecting proxy: it re-signs every site with its own root and includes that root in the chain. Node.js uses its bundled Mozilla root list, not the operating system's store where IT installed the proxy root.
Why it happens
A TLS-inspecting proxy or antivirus
commonZscaler, Netskope, Fortinet, Palo Alto and HTTPS-scanning antivirus products present their own root. Browsers trust it through the OS store; Node.js, by default, does not.
A private service sends its own root
commonInternal servers that include the company root in their chain still need that root trusted on the client; sending it proves nothing.
The root is installed in the OS, but Node.js uses its own list
occasionalNode.js ships Mozilla's roots inside the binary and ignores the system store unless --use-system-ca or NODE_USE_SYSTEM_CA=1 is set.
How to fix it
- 1.
See who issued the chain
Walk the peer certificate chain from a public site. If the top is named after your company or a security vendor, trust that root; if it is unknown, investigate before trusting anything.
bash openssl s_client -connect registry.npmjs.org:443 -servername registry.npmjs.org -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # The last line pair shows the root the chain ends in (s: and i: identical) - 2.
Add the root with NODE_EXTRA_CA_CERTS
The certificates in the file are added to the built-in roots for every TLS connection of the process, including npm, yarn and fetch. Set it before the process starts.
bash export NODE_EXTRA_CA_CERTS=/etc/ssl/private-ca/corp-proxy-root.pem node app.js - 3.
Use the operating system's store
--use-system-ca or NODE_USE_SYSTEM_CA=1 makes Node.js accept roots the OS trusts, so the proxy root deployed by IT works without copying files.
bash # Managed machines already trust the proxy root in the OS store node --use-system-ca app.js NODE_USE_SYSTEM_CA=1 npm run build - 4.
Temporary diagnostic: NODE_TLS_REJECT_UNAUTHORIZED=0
Disables certificate verification for the whole process. Only use it for one run to confirm the cause; behind an intercepting proxy it also hides any attacker that is not the proxy.
bash # Temporary diagnostic only: confirms trust is the only problem, then remove it NODE_TLS_REJECT_UNAUTHORIZED=0 node app.js