Node "unable to verify the first certificate" Behind Corporate Antivirus or a Proxy — Diagnose the Intercepting CA and Fix It With --use-system-ca, Not NODE_TLS_REJECT_UNAUTHORIZED
When npm, fetch or gcloud fail TLS verification on one machine only, something is intercepting HTTPS and re-signing it. How to see whose certificate you are being handed, why Node ignores the Windows store by default, and the one flag that fixes it safely.
The error looks like a bug in your code or a broken package registry: UNABLE_TO_VERIFY_LEAF_SIGNATURE, unable to verify the first certificate, or CERTIFICATE_VERIFY_FAILED from a Python or gcloud tool. It happens on one machine, or in one shell, and the same command works elsewhere. Almost always the cause is that something on the machine is sitting between you and the internet, decrypting HTTPS and re-signing it with its own certificate. Antivirus web shields do this on Windows; corporate proxies do it on managed laptops.
Confirm who is intercepting
Ask the server for its certificate and read the issuer. With OpenSSL:
openssl s_client -connect registry.npmjs.org:443 -servername registry.npmjs.org </dev/null 2>/dev/null | openssl x509 -noout -issuer
If the issuer is the real certificate authority, there is no interception and the problem is elsewhere. If it names your antivirus vendor or your company's proxy, you have found it. The same check from a browser, by clicking the padlock, shows the same issuer, and the browser is happy because the interceptor installed its root certificate in the operating system's trust store.
Why Node fails when the browser works
Browsers and most Windows-native tools use the operating system's certificate store, which contains the interceptor's root. Node ships its own bundled list of root certificates and, by default, ignores the system store. So Node sees a chain signed by an unknown root and refuses. Tools that bundle their own roots, such as gcloud's Python runtime, fail for the same reason.
The safe fix
Tell Node to also trust the system store:
node --use-system-ca script.js
Or for every Node process in the shell, including npm:
export NODE_OPTIONS=--use-system-ca
Verification still happens; Node simply trusts the roots your operating system trusts. Recent Node versions support this flag on Windows and macOS. The older alternative, NODE_EXTRA_CA_CERTS pointing at the interceptor's exported root certificate, also works and is the option for Node versions without the flag.
The fix to avoid
NODE_TLS_REJECT_UNAUTHORIZED=0 disables verification entirely for every connection. It makes the error go away and also makes every HTTPS request from that process trivially interceptable by anyone. It is never the right answer outside a throwaway experiment.
Where else this bites
- npm install fails on every package fetch;
npm config set cafileis an alternative if the flag is unavailable. - gcloud ignores the system store and needs
core/custom_ca_certs_file, but that setting is global and will break the same gcloud for shells that are not intercepted. A cleaner workaround is to keep gcloud for authentication only and call Google APIs with a tool that uses the system store, such as PowerShell'sInvoke-RestMethodwith a bearer token. - Sandboxed tools run by editors and AI assistants are often intercepted even when your own terminal is not, because they connect through a different network path. Check the issuer from inside the sandbox before blaming the project.
Common mistakes
- Editing package.json or app code to work around what is a machine-level network condition.
- Assuming the registry is down. Check the issuer first; it takes ten seconds.
- Leaving verification disabled after the experiment ends.
Summary
Read the certificate issuer to confirm interception, then run Node with --use-system-ca (or NODE_EXTRA_CA_CERTS) so it trusts the same roots the operating system does. Never turn verification off. The related Windows line-ending pitfalls for the same kind of "works on my machine" bug are in Git CRLF line endings on Windows with Node.