<!-- https://pkizerotohero.com/errors/node-unable-to-verify-leaf-signature/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> Fix Node.js UNABLE_TO_VERIFY_LEAF_SIGNATURE, unable to verify the first certificate: the server omits its intermediate. Serve the full chain.

Source: https://pkizerotohero.com/errors/node-unable-to-verify-leaf-signature/ · Last reviewed: 2026-10-20

- 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.

[Check yours: Chain checker](https://pkizerotohero.com/tools/chain/)

## 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](https://nodejs.org/api/tls.html#x509-certificate-error-codes).

```text
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](https://pkizerotohero.com/handbook/glossary/#leaf-certificate). To check its signature, Node.js needs the certificate of the CA that signed it: normally an [intermediate certificate](https://pkizerotohero.com/handbook/glossary/#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](https://pkizerotohero.com/handbook/glossary/#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:

```bash
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:

```bash
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](https://pkizerotohero.com/errors/openssl-verify-error-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:

```bash
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:

```bash
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](https://nodejs.org/api/cli.html#node_extra_ca_certsfile)). 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`](https://nodejs.org/api/cli.html#--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](https://pkizerotohero.com/errors/node-self-signed-cert-in-chain/) or [UNABLE\_TO\_GET\_ISSUER\_CERT\_LOCALLY](https://pkizerotohero.com/errors/npm-unable-to-get-issuer-cert-locally/).

Warning

Why NODE\_TLS\_REJECT\_UNAUTHORIZED=0 is a trap

`NODE_TLS_REJECT_UNAUTHORIZED=0` makes Node.js accept any certificate for every connection in the process, not just this one; `rejectUnauthorized: false` does the same for every request that uses it. Node.js prints a warning for a reason: your tokens and data can go to anyone who intercepts the connection. The setting also tends to survive into production. Fix the chain on the server, or add the exact certificate you need with `NODE_EXTRA_CA_CERTS`.

## Check yours

*Interactive tool: Chain checker (runs in the browser at [/tools/chain/](https://pkizerotohero.com/tools/chain/))*

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

- [Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/): why browsers hide this mistake and Node.js does not.
- Related: [verify error:num=21](https://pkizerotohero.com/errors/openssl-verify-error-21/) (the same error in OpenSSL).

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/errors/node-unable-to-verify-leaf-signature/).

Last reviewed 20 October 2026Edition 0.9

## Learn why it happens

- [Chapter 13: Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/)

## Fix it step by step

- [How to build the full chain file your server needs](https://pkizerotohero.com/guides/build-fullchain/)
- [How to make Python, Node.js and npm trust a private CA](https://pkizerotohero.com/guides/trust-ca-python-node/)
- [How to install a certificate in nginx](https://pkizerotohero.com/guides/install-certificate-nginx/)

## Everything on this topic

- [Chains and trust stores](https://pkizerotohero.com/topics/chains-and-trust/)
