Error: self-signed certificate in certificate chain

Node.js · updated 2026-11-04

Raised by Node's TLS layer (https, fetch, database and SDK clients) when a certificate in the chain is signed by a CA that is not in Node's bundled CA list. The code is SELF_SIGNED_CERT_IN_CHAIN; with fetch it appears as the cause of TypeError: fetch failed. It often works in a browser on the same machine, because browsers use the operating system's trust store and Node, by default, does not.

Error: self-signed certificate in certificate chain
    at TLSSocket.onConnectSecure (node:_tls_wrap:1677:34)
    at TLSSocket.emit (node:events:519:28)
    at TLSSocket._finishInit (node:_tls_wrap:1076:8) {
  code: 'SELF_SIGNED_CERT_IN_CHAIN'
}

Quick fix

Give Node the CA that signed the chain: export NODE_EXTRA_CA_CERTS=/path/to/ca.pem before the process starts (it is read once at startup). On Node 23.8+ you can start with --use-system-ca instead (or NODE_USE_SYSTEM_CA=1 on 22.19+ and 24.6+) to trust the operating system's store, where IT usually installs the corporate CA. Don't set NODE_TLS_REJECT_UNAUTHORIZED=0: it turns off verification for every connection.

  1. Check where it happens: only behind the office network or VPN points to a proxy that inspects TLS; only for one internal host points to a private CA.
  2. Get the CA certificate as PEM (from IT, from the internal service, or exported from the system keychain).
  3. Set NODE_EXTRA_CA_CERTS to that file in the environment that starts Node (Dockerfile ENV, systemd unit, CI variables).
  4. Or, on Node 23.8 or later, start with --use-system-ca so Node trusts the OS store.
  5. Restart the process: the variable is only read at startup.
dockerfile
FROM node:22-slim
# The internal CA that signs our services' certificates
COPY certs/internal-ca.pem /usr/local/share/ca/internal-ca.pem
ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca/internal-ca.pem
WORKDIR /app
COPY . .
CMD ["node", "server.js"]

Why it happens

Corporate proxy inspecting TLS

A network proxy re-signs every site with the company's own CA. Browsers trust it through the OS store; Node uses its bundled list and refuses it.

Private CA on internal services

Internal APIs, databases or a self-hosted registry use certificates from a company CA that public CA lists do not include.

A development certificate

A local HTTPS server with a self-signed or mkcert certificate. A single self-signed certificate (not a chain) shows DEPTH_ZERO_SELF_SIGNED_CERT, "self-signed certificate".

The server sends an incomplete chain

A missing intermediate certificate gives "unable to verify the first certificate" (UNABLE_TO_VERIFY_LEAF_SIGNATURE). Browsers often fetch the missing intermediate on their own; Node does not. That one is fixed on the server, by serving the full chain.

Other fixes

  • For one client only, pass the CA in its options (for example the ca option of https.Agent or a database driver) instead of trusting it process-wide.
  • An expired certificate on the server reads "certificate has expired" (CERT_HAS_EXPIRED): renew it; no client setting fixes that safely.

Same error, other wording

  • Error: unable to verify the first certificate (code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE')
  • Error: self-signed certificate (code: 'DEPTH_ZERO_SELF_SIGNED_CERT')
  • Error: certificate has expired (code: 'CERT_HAS_EXPIRED')

Let your coding agent catch the next one

Runheed records this error in production with the stack trace, the breadcrumbs before it and the agent session that shipped the code, and serves it to Claude Code, Cursor or any MCP client. With it connected, you can ask:

> Find the "self-signed certificate in certificate chain" errors in production, tell me which host they come from, and set up NODE_EXTRA_CA_CERTS (or the client's ca option) with the right CA instead of turning off certificate checks.

Plans from $29/moSee what your agent readsSee the setupCancel any time from the billing portal

Related errors

More in Network and HTTP

References: Node.js docs: NODE_EXTRA_CA_CERTS and --use-system-ca, Node.js docs: TLS (X509 certificate error codes)