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
- Platforms
- Windows
- macOS
- Linux
- Java
- Python
- Node.js
- Level
- Practitioner · 4 steps
On this page
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. 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, 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:
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:
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:
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
openssl x509 -in corp-root.pem -noout -subject -issuer -fingerprint -sha256 -ext basicConstraints
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, macOS, Linux |
| curl | System store on Linux; curl.exe on Windows uses the Windows store; or --cacert / CURL_CA_BUNDLE |
--cacert replaces |
curl docs |
| git | System store on Linux; git config --global http.sslBackend schannel on Windows; or http.sslCAInfo |
http.sslCAInfo replaces |
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 |
| 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 |
| Java | Import into cacerts or the application’s trust store |
Adds | Java guide |
Build a combined bundle yourself when you need one:
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.
Check it worked
Run the strict test against a public site with the root as the only trust anchor:
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:
# 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:
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? shows how to ask each runtime. The errors read
self-signed certificate in certificate chain,SELF_SIGNED_CERT_IN_CHAIN,UNABLE_TO_GET_ISSUER_CERT_LOCALLY,CERTIFICATE_VERIFY_FAILED,PKIX path building failedorx509: certificate signed by 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: 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 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? explains trust stores and TLS inspection, Certificate chains explains path validation, and The TLS 1.3 handshake explains where the certificate check sits in a connection.
Go deeper
- curl: SSL certificate verification: how curl finds CA certificates, and
CURL_CA_BUNDLE. - Node.js command-line options:
NODE_EXTRA_CA_CERTSand--use-system-ca. - Requests: SSL certificate verification:
REQUESTS_CA_BUNDLE. - git-config:
http.sslCAInfoandhttp.sslBackend. - Microsoft Learn: Export-Certificate: exporting a certificate from a Windows store.
- RFC 5280, 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.