Views: 19

UNABLE_TO_VERIFY_LEAF_SIGNATURE (Node.js) — fix

UNABLE_TO_VERIFY_LEAF_SIGNATURE means Node can't reach a trusted root. Fix the server intermediate or NODE_EXTRA_CA_CERTS. Free instant check, no sign-up.

Check your domain for this issue now

Free, no sign-up. Runs the exact check this guide describes and shows what to fix.

Problem

Node.js rejects the connection with Error: unable to verify the first certificate and code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'. Node received the server’s certificate but could not build a chain from it up to a certificate authority it trusts. Almost always this means one specific link is missing — the intermediate certificate that connects the server’s leaf to a public root — and it’s missing in one of two places: on the server (it sent the leaf without the intermediate) or on your machine (Node’s trust store doesn’t hold the root, or a proxy re-signed the traffic with a CA Node doesn’t carry). The certificate itself is usually valid. Sorting out which side is missing the link is the entire task.

Symptoms

  • The error object shows code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'; the message is often unable to verify the first certificate. npm install surfaces the same thing as npm ERR! code UNABLE_TO_VERIFY_LEAF_SIGNATURE when a registry or proxy serves a chain Node can’t complete.
  • Sibling codes point at different problems: SELF_SIGNED_CERT_IN_CHAIN (a private/interception CA in the path), DEPTH_ZERO_SELF_SIGNED_CERT (the endpoint presented its own self-signed cert), CERT_HAS_EXPIRED (a cert in the chain is past its validity date), and ERR_TLS_CERT_ALTNAME_INVALID (the cert is trusted but issued for a different hostname). Each has its own fix.
  • If it fails for one host but others work: the problem is that host’s chain — almost always a missing intermediate on the server.
  • If it fails for every HTTPS host, or only inside your corporate network: the problem is Node’s trust store on your machine — a proxy root Node can’t see.

Top 3 Causes

  1. The server isn’t sending its intermediate certificate - The most common cause by far. The server presents only its leaf and omits the intermediate that chains it to a trusted root. Browsers hide this by fetching the missing intermediate on their own (AIA fetching); Node does not, so it can’t complete the chain and throws UNABLE_TO_VERIFY_LEAF_SIGNATURE. The fix is on the server — deploy the full chain (leaf + intermediate), not the leaf alone. This is the one to check first, because it’s the one you can prove from the outside.
  2. A proxy or antivirus is intercepting TLS - Corporate middleboxes and “HTTPS scanning” antivirus decrypt outbound traffic and re-sign it with their own private CA. That CA lives in the OS store on a managed machine, but Node verifies against its own bundled Mozilla roots, not the OS store — so it sees an issuer it can’t find. This usually presents as SELF_SIGNED_CERT_IN_CHAIN, and it’s why the failure often appears only on the office network or VPN.
  3. Node’s trust store is missing a legitimately-needed root - Beyond interception, any custom or internal root Node needs — a private PKI, an internal registry, an appliance — is absent from the bundled Mozilla store. Node has no idea it exists until you tell it, so the leaf it signs looks unverifiable even though the setup is correct for your environment.

Diagnose with DechoNet

  • SSL Check shows the exact chain the server sends over the wire — whether the intermediate is present or the server is shipping the leaf alone. If the chain is incomplete here, you’ve confirmed cause #1 and the fix is on the server. If the chain looks complete from the public internet but Node still fails on your machine, the problem is local: a proxy re-signing your traffic or a missing internal root (cause #2 or #3).
  • HTTP Check confirms the endpoint actually responds once the certificate question is set aside, so you don’t chase a trust error on a host that’s also down.

Resolution Checklist

  • Read the scope first. One host failing while others work → server chain problem. Every host failing, or only on the corporate network → Node’s trust store. This one question routes the entire fix.
  • For one failing host, look at the served chain: openssl s_client -connect YOUR_DOMAIN:443 -servername YOUR_DOMAIN -showcerts. If you see only one CERTIFICATE block, the intermediate is missing — reinstall the certificate as a fullchain bundle on the server and re-run SSL Check to confirm the intermediate now appears. This is the correct fix even though it lives on the server, not in your code.
  • For a legitimate extra root (corporate proxy, internal CA), add that root without disabling anything: NODE_EXTRA_CA_CERTS=/path/to/extra-ca.pem node app.js. The file is one or more PEM certificates, and Node reads the variable only at process launch — set it before you start Node, and restart after changing it, or it silently has no effect.
  • Prefer fixing trust over patching individual requests. If you must scope trust to one client, pass the CA explicitly (https.request({ ca: fs.readFileSync('ca.pem') })) rather than reaching for a global switch.
  • Do not set NODE_TLS_REJECT_UNAUTHORIZED=0. It disables verification for the whole process and every host it talks to — Node prints a warning saying exactly that. If you used it to get unblocked, remove it and add the specific root instead.
  • If the code is actually CERT_HAS_EXPIRED, fix the date, not the trust: check the server’s certificate expiry (SSL Check shows it) and the client machine’s clock — a wrong system clock makes a valid certificate look expired.

When to Escalate

  • If SSL Check shows a complete, correct chain from the public internet but Node still fails only on one machine or network, the CA doing the re-signing is on that network — a proxy or antivirus. Distributing the right root to trust is an endpoint/IT decision, not a per-app flag.
  • If the endpoint genuinely uses a private or self-signed CA (an internal service, an appliance, a dev environment), trust that specific CA via NODE_EXTRA_CA_CERTS or the ca option — don’t turn verification off globally. Every NODE_TLS_REJECT_UNAUTHORIZED=0 that ships is a process that will accept an attacker’s certificate as readily as your server’s, silently, for as long as it runs.

Related Tools

Related Guides

Share this guide

[Ad] Guide Detail Inline
← Back to All Guides