• Node.js
  • Exact text checked with the real tool

UNABLE_TO_VERIFY_LEAF_SIGNATURE

Also seen in: npm, yarn, axios, fetch (undici), Electron

In one sentence

Node.js could not find the issuer of the server's own certificate: the server almost certainly sends its certificate without the intermediate, which browsers hide and Node.js does not.

Where you see it

In Node.js when you call an HTTPS server with https.request, fetch, axios or any other client, and in npm and yarn. The code comes from OpenSSL’s verify error 21; Node.js lists these codes in its TLS documentation.

TypeError: fetch failed
    at node:internal/deps/undici/undici:13510:13
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async file:///home/dev/app/client.mjs:1:1 {
  [cause]: Error: unable to verify the first certificate; if the root CA is installed locally, try running Node.js with --use-system-ca
      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: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'
  }
}

Older Node.js versions print only unable to verify the first certificate, without the hint about --use-system-ca. With fetch, the useful part is always in [cause].

What it means

The “leaf” is the server’s own certificate, the leaf certificate. To check its signature, Node.js needs the certificate of the CA that signed it: normally an intermediate certificate that the server must send. Node.js found that issuer neither in what the server sent nor in its trusted roots, so it could not verify even the first link of the certificate chain. Chrome and Edge can fetch a missing intermediate on their own, so the site can look fine in a browser.

Likely causes

The server does not send its intermediate certificate

This is the cause in nearly every case. Count the certificates the server sends:

openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERTIFICATE'

1 means only the server’s own certificate arrived.

Fix it on the server. Serve the certificate followed by the intermediate:

cat api.example.com.crt issuing-ca.crt > fullchain.pem
  • nginx: ssl_certificate /etc/ssl/api.example.com/fullchain.pem; (certbot users: fullchain.pem, not cert.pem).
  • Apache 2.4.8 and later: SSLCertificateFile with the full chain file.
  • IIS: import the intermediate into the computer’s “Intermediate Certification Authorities” store.
  • A Node.js server you run: pass the chain in cert: https.createServer({ key, cert: fs.readFileSync('fullchain.pem') }, app).

Then run the count again: you should see 2 or more. Where to get the intermediate is explained on the verify error:num=21 page.

The server sends the wrong intermediate

If the count is 2 but the error remains, the second certificate may belong to another CA or an older issuance. Compare the issuer of the first certificate with the subject of the second; they must match:

openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null | grep -E '^ [0-9]+ s:|^   i:'

You cannot change the server (stopgap only)

If a third party’s server is broken and you must keep working until they fix it, add the missing intermediate, together with its root if that is not a public root, to the certificates Node.js trusts:

NODE_EXTRA_CA_CERTS=/etc/ssl/extra/issuing-ca.pem node app.js

NODE_EXTRA_CA_CERTS adds PEM certificates to Node.js’s built-in roots when the process starts (Node.js CLI documentation). Treat it as temporary and tell the server’s owner: it breaks again when the CA rotates its intermediates.

The hint mentions --use-system-ca: when does that help?

Recent Node.js versions suggest --use-system-ca (available from Node.js 22.15 and 23.8), which makes Node.js also trust the roots in the operating system’s store. That helps when your company root is installed on the machine. It does not help when the server leaves out its intermediate. If the server sends its full chain and you still get an error, see SELF_SIGNED_CERT_IN_CHAIN or UNABLE_TO_GET_ISSUER_CERT_LOCALLY.

Check yours

Try it: Chain checkerOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

Every certificate the server sends, leaf first. The output of openssl s_client -showcerts -connect example.com:443 works as it is: we ignore the text around the certificates.

Paste the certificates from openssl s_client -showcerts. The checker shows the chain stopping after the server’s certificate and which issuer is missing.

Learn why

In the gameLevel 13: The chainChain Bridge, Keystone. In development

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9