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
- Leaf — the site’s certificate (name matches the host).
- Intermediate(s) — CA certificates that signed the leaf.
- 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
- Corporate HTTPS inspection — the proxy re-signs traffic with a company CA. Browsers got the CA via MDM; Node/Python/Docker images did not.
- Incomplete chain on the server — leaf only, missing intermediate; some clients guess, others fail (sometimes as unable to get local issuer).
- Local or staging self-signed cert — mkcert, Traefik defaults, webpack-dev-server HTTPS.
- Wrong CA bundle in the container — slim images without updated
ca-certificates, orSSL_CERT_FILEpointing at an empty/custom file.
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
- 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.pemfor Node (appends; does not replace). - Fix the server chain — serve leaf + intermediates (fullchain.pem). See also unable to get local issuer certificate.
- Local HTTPS — use mkcert or a real Let’s Encrypt cert in staging; add the mkcert root to the trust store once.
- Containers —
update-ca-certificatesafter 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
- unable to get local issuer certificate — incomplete chain / missing CA.
- certificate has expired — dates, not trust.
- ERR_CERT_COMMON_NAME_INVALID — name mismatch.
- SSL wrong version number — HTTPS spoken to plain HTTP.
- Python SSL certificate verify failed — same class of problem in Python.
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 .