Skip to content
Nilay Kabariya

Entry No.068·Fixes··3 min read

Fix: self-signed certificate in certificate chain (npm)

npm install and fetch() fail with SELF_SIGNED_CERT_IN_CHAIN behind a company proxy. Reproduced on Windows and Linux: which fixes work, and two that don't.

The error, verbatim

Jump to the fix ↓
npm error code SELF_SIGNED_CERT_IN_CHAIN
npm error errno SELF_SIGNED_CERT_IN_CHAIN
npm error request to https://registry.npmjs.org/is-number failed, reason: self-signed certificate in certificate chain
✓ Reproduced on Windows (GitHub windows-latest), Ubuntu, Windows 11 Pro

Tested on

Node / npm
24.14.0 / 11.9.0
OS
Windows (GitHub windows-latest), Ubuntu, Windows 11 Pro
Setup
local HTTPS proxy with a private root CA

✓ Last re-run on GitHub Actions · see the run · all repros

Contents
  1. Step 1: get the company’s root certificate as a file01
  2. Step 2: point npm and Node at it02
  3. Two ways it still fails with the fix in place03
  4. What I didn’t use04
  5. How this was tested05

This error almost never means something is wrong with npm. Something between you and the registry, usually a company proxy or VPN that inspects HTTPS traffic, opens each connection and re-signs it with its own root certificate. Your browser trusts that root because IT installed it in Windows. Node doesn’t use the Windows certificate store; it ships its own list, so it sees a root it has never heard of and refuses.

To reproduce it, I ran a small HTTPS proxy on localhost that forwards to the real npm registry and signs everything with a private root CA, the way an inspecting proxy does. Then I pointed npm and Node at it, on GitHub’s Windows and Ubuntu runners. Both gave the same error:

npm error code SELF_SIGNED_CERT_IN_CHAIN
npm error request to https://localhost:8443/is-number failed, reason: self-signed certificate in certificate chain

fetch() in Node failed the same way: fetch failed, with cause.code SELF_SIGNED_CERT_IN_CHAIN.

Step 1: get the company’s root certificate as a file

If your browser can open the site, the root is already in Windows. Export it as a .pem file. In PowerShell, find it by name (ask IT if you don’t know it), then write it out:

$c = Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -match 'YourCompany' } | Select-Object -First 1
$pem = "-----BEGIN CERTIFICATE-----`n" + [Convert]::ToBase64String($c.RawData, 'InsertLineBreaks') + "`n-----END CERTIFICATE-----"
Set-Content C:\certs\corp-root.pem $pem -Encoding ascii

This only reads a certificate that’s already installed. It doesn’t change what Windows trusts.

Step 2: point npm and Node at it

For npm and everything else that runs on Node (npx, fetch(), build tools), set the variable before starting Node:

$env:NODE_EXTRA_CA_CERTS = "C:\certs\corp-root.pem"

For npm only, put it in the project’s .npmrc, or pass it once:

cafile=C:\certs\corp-root.pem

To keep the variable for new terminals, set it as a user environment variable (System Properties → Environment Variables) rather than in one PowerShell window.

Two ways it still fails with the fix in place

What I didn’t use

npm config set strict-ssl false and NODE_TLS_REJECT_UNAUTHORIZED=0 both made the install succeed. They do it by switching off certificate checks entirely, so anything on the network path could serve you a different package. NODE_TLS_REJECT_UNAUTHORIZED=0 was also slow: the same install took about 2 minutes instead of under a second, on both systems. Use the root certificate instead.

How this was tested

Node 24.14.0 and npm 11.9.0 on GitHub Actions’ windows-latest and ubuntu-latest runners, October 15, 2026. A small Node HTTPS proxy on localhost:8443 forwarded to registry.npmjs.org and presented a certificate signed by a private root CA created for the test; each case installed is-number through it in a fresh project. The export and the Avast observation were on a Windows 11 Pro PC. The script and workflow are public. Every message above is the real output, except that the error at the top shows the registry’s address where the test used the proxy’s.

— N.K., end of entry No.068

Useful? Pass it on:Post on XFollow @EmotionalMatter

Related entries

  1. No.093

    Fix: bigint Failed to load bindings, pure JS will be used

    bigint: Failed to load bindings, pure JS will be used. Why npm 12 shows it on every install, whether it matters, and how to get the native build back.

    > bigint: Failed to load bindings, pure JS will be used (try npm run rebuild?)

    FIXED3 minWindows
  2. No.072

    npm error code ENOENT, and spawn npx ENOENT on Windows

    Read the path line first. On Windows, spawn npx ENOENT has a twist: adding .cmd now fails with EINVAL. Four ENOENT causes reproduced, each with its fix.

    > npm error code ENOENT

    FIXED3 minWindows
  3. No.071

    Fix: JavaScript heap out of memory (Node.js, tested)

    FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory. Default limits measured, and two ways the usual fix silently fails.

    > FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

    FIXED2 minWindows

Post card · Every Sunday

What broke this week.

One email on Sundays: the Node, TypeScript, Python and AI SDK releases that broke builds that week, each one reproduced, with a link to the fix that worked. Never shared, and you can leave any time.

— Nilay

or follow by RSSor on X

By subscribing you agree to the privacy note. One click to leave.

tip: paste the exact error text