Home / Tutorials / HTTP

HTTP · Network · Degraded

Fix: certificate verify failed: self signed certificate in certificate chain

Error certificate verify failed: self signed certificate in certificate chain

By the Erzon engineering team · A step-by-step fix, not a forum thread

This OpenSSL verification error means your client received a certificate chain in which some certificate is signed by an issuer the client does not trust, ending at a self-signed root it does not recognize. In practice it almost always means one thing: something on the network path (a corporate TLS-inspection proxy, or your own private CA) is presenting its own chain, and the machine’s client software has not been told to trust it. The error is TLS working as designed; the fix is adding the right trust, never removing verification.

certificate verify failed: self signed certificate in certificate chain

What this error means

TLS clients verify servers by walking the presented certificate chain up to a root CA in their trust store. “Self signed certificate in certificate chain” means the chain terminates at a root the client does not have. Distinguish it from the sibling error “self signed certificate” (no chain, the server itself presents a lone self-signed cert): the “in certificate chain” wording tells you a CA structure exists, it is just not a publicly trusted one.

The subtlety that burns teams is that there is no single trust store. The operating system has one, but Python (certifi), Node, Java, Docker, and many CLI tools each carry or configure their own. That is why the browser works while pip install fails on the same machine, on the same network, to the same host.

Common causes

  • A corporate TLS-inspection proxy (Zscaler, Netskope, Palo Alto SSL decryption) re-signing all HTTPS traffic with its own CA, which is missing from your tool’s trust store.
  • An internal service using a company private CA that was distributed to browsers but not to language runtimes or CI images.
  • A fresh machine, container image, or CI runner that never received the corporate or private CA at all.
  • Antivirus software with HTTPS scanning enabled, interposing its own certificate on outbound TLS.
  • A server actually misconfigured to send a self-signed root in its chain instead of a proper public chain.

How to fix it

STABILIZE first. If a production job is blocked, point the failing client at a CA bundle that includes the missing root (details below). Do not ship a verification-disabled workaround, even briefly; those never stay temporary.

  1. Look at the chain the client is actually receiving. The issuer names tell you immediately whether a corporate proxy is in the path.
openssl s_client -connect pypi.org:443 -servername pypi.org -showcerts </dev/null 2>/dev/null | grep -E "s:|i:"

A real chain shows public CAs (for example issuers from Let’s Encrypt or DigiCert). If you see your employer’s name, Zscaler, or similar as the issuer, traffic is being intercepted and that interceptor’s CA is what you need to trust.

  1. Extract the intercepting root (your IT team can also provide it as a file, which is preferable) and confirm which store your failing tool uses.
openssl s_client -connect pypi.org:443 -servername pypi.org -showcerts </dev/null 2>/dev/null \
  | awk '/BEGIN CERT/,/END CERT/' > corp-chain.pem
python -c "import certifi; print(certifi.where())"

DIAGNOSE by scope. Fails only on the corporate network or VPN: TLS inspection, trust the corporate CA. Fails only for internal hostnames: private CA, distribute its root. Fails everywhere including from clean networks and the issuer is genuinely the server’s own name: the server chain itself is broken, and the fix is on the server (install a certificate from a real CA; Let’s Encrypt is free).

FIX by adding the CA to the system store, then wiring runtimes that ignore the system store. On Debian/Ubuntu:

cp corp-root.crt /usr/local/share/ca-certificates/corp-root.crt
update-ca-certificates

Then per runtime, point at the system bundle or add the CA explicitly:

# Python (requests/pip)
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
pip config set global.cert /etc/ssl/certs/ca-certificates.crt

# Node.js
export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root.crt

# Java
keytool -importcert -cacerts -alias corp-root -file corp-root.crt -storepass changeit -noprompt

For containers and CI, bake the CA into the base image (copy the cert and run update-ca-certificates in the Dockerfile) so every build and job inherits it instead of each pipeline rediscovering this error.

How to prevent it

  • Distribute the corporate or private CA through the same automation that provisions machines, images, and CI runners, covering system, Python, Node, and Java stores.
  • Keep one documented internal CA bundle location and point runtime env vars (REQUESTS_CA_BUNDLE, NODE_EXTRA_CA_CERTS) at it in the standard shell profile.
  • Ban verification-disabling flags in code review and CI (grep for verify=False, NODE_TLS_REJECT_UNAUTHORIZED, and curl -k).
  • For internal services, issue certificates from your private CA with proper chains rather than lone self-signed certs.
  • When IT rotates the inspection CA, treat it like any credential rotation: announce it and update the distributed bundle.

When to call a senior engineer

Call for help when TLS interception breaks tooling across many teams and machines and the CA needs distributing systematically rather than machine by machine, when mutual TLS or certificate pinning conflicts with an inspection proxy, or when you need a private PKI designed properly with issuance, rotation, and revocation instead of hand-copied certs. Erzon engineers can map exactly which store each failing tool reads, automate trust distribution across your fleet and CI, and remove every verify=False that has crept into the codebase along the way.

Related errors we fix

Still down after this?

Want a senior engineer to just fix it?

Paste this exact error into the form. A senior engineer replies within one business hour with what is likely wrong and a flat quote. Triage is free.

Book a fix

← Back to all error fixes

Still down?

Get a senior engineer on it now

Leave your email and a line about what is happening. A senior engineer replies within one business hour with what is likely wrong and a flat quote. Triage is free.

Or email [email protected]. Same engineers, same one-hour clock.