self-signed certificate in certificate chain (NODE / TLS)

TLS found a certificate that was signed by itself (or an untrusted private CA) in the chain. Browsers and Node refuse the connection unless you trust the right CA — not by switching verification off forever.

How it shows up

Error: self-signed certificate in certificate chain
code: 'SELF_SIGNED_CERT_IN_CHAIN'

# OpenSSL wording variants:
# verify error:num=19:self signed certificate in certificate chain
# depth=0 verify error: self-signed certificate

Fetch, axios, npm, yarn, pip, curl and browsers can all hit the same underlying chain problem with different banners.

What the chain is supposed to look like

  1. Leaf — the site’s certificate (name matches the host).
  2. Intermediate(s) — CA certificates that signed the leaf.
  3. Root — in the client trust store (not usually sent by the server).

A self-signed leaf (depth 0) is common in local HTTPS. A self-signed certificate in the chain often means a private root or proxy CA appeared where a public CA should be — and your OS/Node does not trust that private root yet.

Most common real-world causes

Diagnose with OpenSSL

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
# Look at "depth=" verify errors and which CN signed which

Compare with a browser: if Chrome is happy and Node is not, you almost certainly lack the corporate/private CA in the Node trust store.

Fixes that keep verification on

  1. Trust the company CA — export the root/intermediate as PEM, install into the OS trust store, or set NODE_EXTRA_CA_CERTS=/path/to/corp-root.pem for Node (appends; does not replace).
  2. Fix the server chain — serve leaf + intermediates (fullchain.pem). See also unable to get local issuer certificate.
  3. Local HTTPS — use mkcert or a real Let’s Encrypt cert in staging; add the mkcert root to the trust store once.
  4. Containers — update-ca-certificates after copying the corp CA into /usr/local/share/ca-certificates/.

What not to do

# Disables TLS verification for the whole process — MITM-friendly, easy to leak into prod
export NODE_TLS_REJECT_UNAUTHORIZED=0   # avoid

If you must bypass in a one-off debug session, unset it afterwards. Prefer NODE_EXTRA_CA_CERTS or a custom ca option on the HTTPS agent for that one host.

Related errors

Related

Cloudflare Error 525 · Cloudflare Error 526 · TypeError: fetch failed (Node) · unable to get local issuer certificate · certificate has expired · ERR_CERT_COMMON_NAME_INVALID · ERR_SSL_PROTOCOL_ERROR · SSL wrong version number · Mixed Content blocked · What is this?

Questions

What does self-signed certificate in certificate chain mean?

TLS verification found a certificate signed by itself (or an untrusted private CA) while walking the chain. The client does not trust that signer.

How do I fix SELF_SIGNED_CERT_IN_CHAIN in Node?

Install the missing CA (often a corporate root) and use NODE_EXTRA_CA_CERTS, or fix the server to send a complete public chain. Avoid NODE_TLS_REJECT_UNAUTHORIZED=0.

Why does the browser work but Node fails?

The browser received the company CA via OS/MDM policies; Node uses its own CA store unless you add NODE_EXTRA_CA_CERTS or system certs.

Is a self-signed certificate always wrong?

For public production sites, yes — use a public CA. For local dev, self-signed is fine if every client trusts your local root (e.g. mkcert).

Is this the same as certificate has expired?

No. Expiry is about notAfter dates. This error is about trust / chain composition.

Does npm config set strict-ssl false help?

It weakens npm’s TLS checks and is the wrong long-term fix. Trust the right CA instead.

Published .

Try it

Related guides

Newest guides