<!-- https://pkizerotohero.com/guides/corporate-proxy-certificates/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> Fix certificate errors behind a TLS-inspecting proxy: confirm the inspection, get the company root from IT and trust it per tool, never disabling checks.

Source: https://pkizerotohero.com/guides/corporate-proxy-certificates/ · Last reviewed: 2026-10-20

How-to guide · Trust a CA

# How to work behind a TLS-inspecting corporate proxy, safely

**In one sentence:**

You will have confirmed that your traffic is inspected, obtained your company's inspection root through a trusted channel, and made each tool trust it with verification still on.

Task

[Trust a CA](https://pkizerotohero.com/guides/#task-trust)

Platforms

- Windows
- macOS
- Linux
- Java
- Python
- Node.js

Who it is for

- [Developer](https://pkizerotohero.com/start/#developer)
- [IT ops](https://pkizerotohero.com/start/#itops)
- [Expert](https://pkizerotohero.com/start/#expert)

Level

Practitioner · 4 steps

## Before you start

- **The exact error** from the tool that fails, for example curl, git, pip, npm, Java or Docker.
- **A way to reach your IT or security team**, who own the inspection root.
- **Administrator rights** only if you change a system-wide trust store.

Many companies inspect web traffic with [TLS inspection](https://pkizerotohero.com/handbook/glossary/#tls-inspection). The proxy ends your TLS connection, checks the real server’s certificate itself, decrypts and inspects the traffic, then encrypts it again towards you with a certificate it creates on the spot for that site. Those certificates are signed by a company root that IT installs on managed laptops. Browsers use the roots IT adds to the operating system’s [trust store](https://pkizerotohero.com/handbook/glossary/#trust-store), so they work. Tools with their own store do not know that root, so they fail, often with `self-signed certificate in certificate chain`.

Be clear about what this means. The proxy can read everything you send, including passwords and tokens; that is its purpose, and it is legitimate only when your organisation runs it under a published policy. Any root in your trust store can vouch for any website, so trust only the inspection root your organisation controls.

## Steps

### 1. Confirm that your traffic is inspected

Ask who issued the certificate for a well-known public site:

```bash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer
```

Without inspection, the issuer is a public CA. Behind an inspecting proxy, it names your company or its security product. Here is what that looks like, reproduced in our lab with a stand-in root:

```text
subject=CN = example.com
issuer=C = BE, O = Example Corp, CN = Example Corp TLS Inspection Root
```

The full `s_client` output ends with `Verify return code: 19 (self-signed certificate in certificate chain)`, because the proxy sends its root and OpenSSL does not trust it. A name alone proves nothing: confirm with IT that this is their proxy.

### 2. Get the root through a trusted channel

**Best: from IT.** Ask for the inspection root as a PEM file and for its SHA-256 fingerprint, published somewhere you trust, such as the intranet or a ticket.

**If IT already installed it** on your Windows or macOS laptop (your browser works), export it from the operating system’s store. On Windows, in PowerShell:

```powershell
Get-ChildItem Cert:\LocalMachine\Root | Where-Object Subject -like '*Example Corp*' |
  Export-Certificate -FilePath .\corp-root.cer -Type CERT
certutil -encode corp-root.cer corp-root.pem
```

`Export-Certificate -Type CERT` writes DER, and `certutil -encode` turns it into PEM. On macOS, `security find-certificate -c "Example Corp TLS Inspection Root" -p /Library/Keychains/System.keychain > corp-root.pem` prints it as PEM. Browsers can also export it: open the certificate viewer for any site, select the top certificate of the chain, and export it.

Do not take the root from the inspected connection alone. A root you received over the very connection you are trying to verify proves nothing until its fingerprint matches what IT publishes.

### 3. Check the file

```bash
openssl x509 -in corp-root.pem -noout -subject -issuer -fingerprint -sha256 -ext basicConstraints
```

```text
subject=C=BE, O=Example Corp, CN=Example Corp TLS Inspection Root
issuer=C=BE, O=Example Corp, CN=Example Corp TLS Inspection Root
sha256 Fingerprint=38:54:01:A3:09:92:DC:C8:5D:C5:F5:8D:26:C7:59:D9:85:0D:7A:33:B1:6E:5A:5C:7F:79:19:82:B1:EC:9F:38
X509v3 Basic Constraints: critical
    CA:TRUE
```

Subject equals issuer, `CA:TRUE`, and the fingerprint matches IT’s. The file is a certificate, not a secret; if it contains `PRIVATE KEY`, stop and tell IT.

### 4. Make each tool trust it

Some settings **add** a root to the tool’s defaults; others **replace** the whole list. A replacement must contain the public roots as well, or public sites will fail off the corporate network and on sites the proxy does not inspect. On Linux, after you add the root to the system store, `/etc/ssl/certs/ca-certificates.crt` (Debian and Ubuntu) is such a combined bundle.

| Tool | Safe setting | Adds or replaces | Details |
| --- | --- | --- | --- |
| Operating system (Windows, macOS, Linux, WSL, containers) | The system trust store | Adds | [Windows](https://pkizerotohero.com/guides/add-root-ca-windows/), [macOS](https://pkizerotohero.com/guides/add-root-ca-macos/), [Linux](https://pkizerotohero.com/guides/add-root-ca-linux/) |
| curl | System store on Linux; `curl.exe` on Windows uses the Windows store; or `--cacert` / `CURL_CA_BUNDLE` | `--cacert` replaces | [curl docs](https://curl.se/docs/sslcerts.html) |
| git | System store on Linux; `git config --global http.sslBackend schannel` on Windows; or `http.sslCAInfo` | `http.sslCAInfo` replaces | [git-config](https://git-scm.com/docs/git-config) |
| Python | Requests: `REQUESTS_CA_BUNDLE`; the `ssl` module: `SSL_CERT_FILE`; both pointing to a combined bundle. Recent pip versions also read the operating system’s store | `REQUESTS_CA_BUNDLE` replaces; `SSL_CERT_FILE` replaces the default file | [Python and Node.js guide](https://pkizerotohero.com/guides/trust-ca-python-node/) |
| Node.js, npm | `NODE_EXTRA_CA_CERTS=/path/corp-root.pem`, or `--use-system-ca` in recent Node.js | Adds | [Python and Node.js guide](https://pkizerotohero.com/guides/trust-ca-python-node/) |
| Java | Import into `cacerts` or the application’s trust store | Adds | [Java guide](https://pkizerotohero.com/guides/add-root-ca-java/) |

Build a combined bundle yourself when you need one:

```bash
cat /etc/ssl/certs/ca-certificates.crt corp-root.pem > ~/ca-bundle-plus-corp.pem
export CURL_CA_BUNDLE=~/ca-bundle-plus-corp.pem
```

WSL distributions and container images have their own Linux stores: the root IT installed in Windows is not in them. Add it there too, and bake it into images at build time.

Warning

Never fix this by switching verification off

`curl -k`, `git -c http.sslVerify=false`, `npm config set strict-ssl false`, Python’s `verify=False`, pip’s `--trusted-host` and `NODE_TLS_REJECT_UNAUTHORIZED=0` all stop checking who you are talking to. On the corporate network that means accepting any interceptor, not just your company’s proxy. Off the network, the setting stays and protects nothing. And these switches spread into scripts, images and pipelines. Trusting the one company root keeps every other check in place: signatures, dates and host names.

## Check it worked

Run the strict test against a public site with the root as the only trust anchor:

```bash
openssl s_client -connect example.com:443 -servername example.com -CAfile corp-root.pem -verify_return_error </dev/null 2>&1 | grep 'Verify return code'
```

Expect `Verify return code: 0 (ok)` on the corporate network. Then run the command that failed. In our lab, the same requests before and after:

```text
# curl, before and after CURL_CA_BUNDLE pointed to a combined bundle
# (the second line is printed by -w '%{http_code} verify=%{ssl_verify_result}')
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
200 verify=0

# Node.js, before and after NODE_EXTRA_CA_CERTS=corp-root.pem
SELF_SIGNED_CERT_IN_CHAIN: self-signed certificate in certificate chain
OK, authorized: true

# Python ssl, before and after loading corp-root.pem
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1000)
with corp root: TLSv1.3
```

The chain checker names known inspection products and shows where a chain ends:

*Interactive tool: Chain checker (runs in the browser at [/tools/chain/](https://pkizerotohero.com/tools/chain/))*

## If something goes wrong

- **The same error after your change:** the tool reads a different store from the one you edited. [Who do we trust?](https://pkizerotohero.com/handbook/trust-stores/) shows how to ask each runtime. The errors read [`self-signed certificate in certificate chain`](https://pkizerotohero.com/errors/curl-self-signed-certificate-in-certificate-chain/), [`SELF_SIGNED_CERT_IN_CHAIN`](https://pkizerotohero.com/errors/node-self-signed-cert-in-chain/), [`UNABLE_TO_GET_ISSUER_CERT_LOCALLY`](https://pkizerotohero.com/errors/npm-unable-to-get-issuer-cert-locally/), [`CERTIFICATE_VERIFY_FAILED`](https://pkizerotohero.com/errors/python-certificate-verify-failed/), [`PKIX path building failed`](https://pkizerotohero.com/errors/java-pkix-path-building-failed/) or [`x509: certificate signed by unknown authority`](https://pkizerotohero.com/errors/go-x509-unknown-authority/).
- **Public sites fail at home, or on sites IT exempts from inspection:** a “replaces” setting holds only the company root. Use a combined bundle.
- **git on Windows shows [`SEC_E_UNTRUSTED_ROOT`](https://pkizerotohero.com/errors/git-schannel-sec-e-untrusted-root/):** the root is not in the Windows store git uses; ask IT to deploy it.
- **One application still fails:** it may pin certificates or ship its own store, so it cannot work through inspection. Ask IT to exempt its domains; do not disable checks.
- **Everything broke on a particular day:** IT may have replaced the inspection root. Get the new one through step 2.

## Why this works

A client accepts a chain when it can link it to a [root](https://pkizerotohero.com/handbook/glossary/#root-certificate) already in its own trust store. Behind the proxy, every chain ends at the company root, so adding exactly that root to the store each tool uses makes validation succeed while the client still checks every signature, date and host name. [Who do we trust?](https://pkizerotohero.com/handbook/trust-stores/) explains trust stores and TLS inspection, [Certificate chains](https://pkizerotohero.com/handbook/certificate-chains/) explains path validation, and [The TLS 1.3 handshake](https://pkizerotohero.com/handbook/tls-handshake/) explains where the certificate check sits in a connection.

## Go deeper

- [curl: SSL certificate verification](https://curl.se/docs/sslcerts.html): how curl finds CA certificates, and `CURL_CA_BUNDLE`.
- [Node.js command-line options](https://nodejs.org/api/cli.html): `NODE_EXTRA_CA_CERTS` and `--use-system-ca`.
- [Requests: SSL certificate verification](https://requests.readthedocs.io/en/latest/user/advanced/#ssl-cert-verification): `REQUESTS_CA_BUNDLE`.
- [git-config](https://git-scm.com/docs/git-config): `http.sslCAInfo` and `http.sslBackend`.
- [Microsoft Learn: Export-Certificate](https://learn.microsoft.com/en-us/powershell/module/pki/export-certificate): exporting a certificate from a Windows store.
- [RFC 5280, section 6](https://www.rfc-editor.org/rfc/rfc5280#section-6): path validation up to a trust anchor.

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/guides/corporate-proxy-certificates/).

Last reviewed 20 October 2026Edition 0.9

## Keep going

### Learn why it works

- [Chapter 12: Who do we trust? CAs, trust stores and self-signed certificates](https://pkizerotohero.com/handbook/trust-stores/)
- [Chapter 13: Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/)
- [Chapter 15: The TLS 1.3 handshake](https://pkizerotohero.com/handbook/tls-handshake/)

### Error pages

- [SSL certificate problem: self-signed certificate in certificate chain](https://pkizerotohero.com/errors/curl-self-signed-certificate-in-certificate-chain/)
- [SSL certificate problem: unable to get local issuer certificate](https://pkizerotohero.com/errors/curl-unable-to-get-local-issuer-certificate/)
- [\[SSL: CERTIFICATE\_VERIFY\_FAILED\] certificate verify failed: unable to get local issuer certificate](https://pkizerotohero.com/errors/python-certificate-verify-failed/)
- [SELF\_SIGNED\_CERT\_IN\_CHAIN](https://pkizerotohero.com/errors/node-self-signed-cert-in-chain/)
- [UNABLE\_TO\_GET\_ISSUER\_CERT\_LOCALLY](https://pkizerotohero.com/errors/npm-unable-to-get-issuer-cert-locally/)
- [PKIX path building failed … unable to find valid certification path to requested target](https://pkizerotohero.com/errors/java-pkix-path-building-failed/)
- [x509: certificate signed by unknown authority](https://pkizerotohero.com/errors/go-x509-unknown-authority/)
- [schannel: SEC\_E\_UNTRUSTED\_ROOT (0x80090325)](https://pkizerotohero.com/errors/git-schannel-sec-e-untrusted-root/)

### Tools

- [Chain checker](https://pkizerotohero.com/tools/chain/): Paste a chain and see which link is missing, expired or out of order.

### Topics

- [Chains and trust stores](https://pkizerotohero.com/topics/chains-and-trust/)

## More guides: Trust a CA

- [How to trust a private root CA on Windows](https://pkizerotohero.com/guides/add-root-ca-windows/)
- [How to trust a private root CA on macOS](https://pkizerotohero.com/guides/add-root-ca-macos/)
- [How to trust a private root CA on Linux](https://pkizerotohero.com/guides/add-root-ca-linux/)
- [How to add a CA to a Java trust store (cacerts)](https://pkizerotohero.com/guides/add-root-ca-java/)
- [How to make Python, Node.js and npm trust a private CA](https://pkizerotohero.com/guides/trust-ca-python-node/)
