# self-signed certificate in certificate chain: causes and fixes

> This OpenSSL error means the chain ends in a root your client does not trust, usually a corporate proxy or internal CA. Trust that root; keep checks on.

Source: https://ssl.sh/errors/self-signed-certificate-in-certificate-chain/
Last updated: 2026-11-10

**`self-signed certificate in certificate chain` means the server's certificate chain ends in a self-signed root certificate that your client does not trust.** The chain itself is intact; the missing piece is trust in its top. On work laptops this is almost always a TLS-inspecting proxy; elsewhere it is an internal CA that was never distributed to the client.

## Quick fix

The root at the top of the chain is the one your client must trust.

Find out which root is at the top of the chain:

```bash
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null \
  | grep -E '^ *[0-9] s:|^ *i:'
```

Look at the last certificate. If its subject and issuer are identical and name your company, a security vendor or an internal CA, that is the root to trust. Get the PEM file from your IT team (or export it from the OS store), check its fingerprint with them, then add it to the tool that failed:

```bash
export NODE_EXTRA_CA_CERTS=/path/company-root.pem          # Node.js
npm config set cafile /path/ca-bundle.pem                 # npm
git config --global http.sslCAInfo /path/ca-bundle.pem    # Git
export REQUESTS_CA_BUNDLE=/path/ca-bundle.pem             # Python requests
curl --cacert /path/ca-bundle.pem https://example.com     # curl
```

For npm, Git, curl and Python, `ca-bundle.pem` should be the system bundle plus your root, because these options replace the default list rather than extend it. `NODE_EXTRA_CA_CERTS` adds to Node's built-in roots.

## Causes

### 1. A TLS-inspecting proxy or antivirus

Corporate gateways and antivirus "HTTPS scanning" decrypt traffic and re-encrypt it with certificates issued by their own root.

- **Confirm:** every external site shows the same unusual root at the top of the chain, and the error disappears on a home network or phone hotspot.
- **Fix:** trust the proxy root per tool as shown above, or ask IT to exempt package registries such as registry.npmjs.org and pypi.org from inspection.

### 2. An internal CA that clients do not know

Internal services (GitLab, Nexus, Harbor, intranet APIs) often use a private CA. Only machines that received its root can verify them.

- **Confirm:** the error appears only for internal hostnames, and the root's subject is your organisation's CA.
- **Fix:** distribute the root through the OS trust store (`update-ca-certificates` or `update-ca-trust`) and through each runtime's own setting.

### 3. The server sends a wrong or obsolete root

Some servers append a root certificate to their chain. If that root is private, expired or a different cross-signed version than the client trusts, the client may stop at it.

- **Confirm:** the [certificate decoder](https://ssl.sh/tools/certificate-decoder/) shows the server chain contains a root, and other clients succeed without it.
- **Fix:** serve the leaf and intermediates only. Rebuild the chain with the [chain builder](https://ssl.sh/tools/chain-builder/).

### 4. A development CA (mkcert, local Kubernetes, test environments)

Local CAs are trusted by browsers after installation, but not by runtimes that ship their own CA list.

- **Fix:** load the dev root explicitly, for example with `NODE_EXTRA_CA_CERTS`, and keep it out of production images.

## Fixes by environment

Every fix adds the proxy's or internal CA's root to the store each tool reads; tools with their own stores need it separately.

**Linux system store (Debian, Ubuntu, Alpine):**

```bash
sudo cp company-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
```

**RHEL, Rocky, Fedora:** copy to `/etc/pki/ca-trust/source/anchors/` and run `sudo update-ca-trust`.

**Windows:** Git for Windows can use the Windows certificate store with `git config --global http.sslBackend schannel`, which picks up roots deployed by Group Policy. Node.js and Python requests still need the settings above.

**macOS:** add the root to the System keychain and mark it trusted; then configure Node, npm and Python separately.

**Java:** import the root into the JVM truststore; see [PKIX path building failed](https://ssl.sh/errors/java-pkix-path-building-failed/).

**Docker builds:** copy the root into the image and run `update-ca-certificates` before any `npm install` or `pip install` step, and pass `NODE_EXTRA_CA_CERTS` where Node runs.

## Don't do this

- **`NODE_TLS_REJECT_UNAUTHORIZED=0`.** Node prints a warning because it disables verification for every TLS connection in the process.
- **`npm config set strict-ssl false`.** Packages are then downloaded without any server authentication, which is exactly the path supply-chain attackers use.
- **`git config --global http.sslVerify false`.** It applies to every remote, not only the one that failed.
- **`verify=False` or `curl -k`.** Same problem: you lose all protection, not only the check that failed.
