Resolving Corporate Proxy and TLS Interception Failures
On the company network, docker pull fails with x509: certificate signed by unknown authority, npm ci inside a build fails with SELF_SIGNED_CERT_IN_CHAIN, pip install reports SSL: CERTIFICATE_VERIFY_FAILED, and everything works at home. The office network runs a TLS-inspecting proxy that re-signs HTTPS traffic with a corporate certificate authority; the laptop's OS trusts that CA, but Docker's daemon, containers and language package managers do not. This page exports the corporate CA once and makes every layer trust it — without turning off verification — as part of common local failure points.
This is one of the most common onboarding blockers at larger companies, and the typical workaround (npm config set strict-ssl false, pip --trusted-host) disables exactly the protection that the corporate proxy is not responsible for once traffic leaves it.
Diagnostic
Confirm interception by looking at who issued the certificate you actually receive, and test each layer:
#!/usr/bin/env bash
set -euo pipefail
echo | openssl s_client -connect registry.npmjs.org:443 -servername registry.npmjs.org 2>/dev/null | openssl x509 -noout -issuer
env | grep -iE '^(https?|no)_proxy=' || echo "no proxy variables set"
docker pull hello-world 2>&1 | tail -1
docker run --rm node:20-bookworm-slim sh -c 'npm view left-pad version' 2>&1 | tail -1
git ls-remote https://github.com/git/git HEAD 2>&1 | tail -1
Expected bad output on the office network:
issuer=C = US, O = Acme Corp, CN = Acme Corp TLS Inspection CA
HTTPS_PROXY=http://proxy.acme.corp:8080
Error response from daemon: Get "https://registry-1.docker.io/v2/": tls: failed to verify certificate: x509: certificate signed by unknown authority
npm error code SELF_SIGNED_CERT_IN_CHAIN
3f2a91c... HEAD
The issuer is the company's inspection CA, not the site's real CA. Git on the host works (it uses the OS store), while Docker's daemon and the container's npm fail because neither trusts the corporate CA.
Root cause
A TLS-inspecting proxy terminates HTTPS, inspects the traffic, and opens a new TLS connection to the client using a certificate it generates on the fly, signed by the corporate CA. IT installs that CA into the laptop's operating system store, so browsers and OS-integrated tools (macOS Keychain-based git, curl built against the system library) trust it. Everything with its own trust store does not: Docker Desktop's VM and Docker Engine's daemon, every container image's /etc/ssl/certs, Node's built-in CA list, Python's certifi bundle, the JVM's cacerts. Each fails with its own flavour of "unknown authority". Disabling verification makes the error go away and also accepts any certificate from anyone, which is a far larger risk than the problem it hides. The correct fix is to distribute the corporate CA to each store, exactly like the local mkcert CA in fixing certificate errors inside containers.
Resolution
- Export the corporate CA from the OS store to a PEM file:
#!/usr/bin/env bash
set -euo pipefail
mkdir -p "$HOME/.config/corp-ca"
case "$(uname -s)" in
Darwin) security find-certificate -a -c "Acme Corp TLS Inspection CA" -p /Library/Keychains/System.keychain > "$HOME/.config/corp-ca/corp-ca.pem" ;;
Linux) cp /usr/local/share/ca-certificates/acme-corp-ca.crt "$HOME/.config/corp-ca/corp-ca.pem" ;;
esac
openssl x509 -in "$HOME/.config/corp-ca/corp-ca.pem" -noout -subject -enddate
- Make the Docker daemon trust it. Docker Desktop on macOS and Windows reads the OS store at startup in current versions — restart Docker Desktop after IT installs the CA. For Docker Engine on Linux, add it to the system store and restart the daemon:
#!/usr/bin/env bash
set -euo pipefail
sudo cp "$HOME/.config/corp-ca/corp-ca.pem" /usr/local/share/ca-certificates/acme-corp-ca.crt
sudo update-ca-certificates
sudo systemctl restart docker
docker pull hello-world | tail -1
- Trust it during image builds with a BuildKit secret, so the CA is available to package managers without being baked into the image:
# syntax=docker/dockerfile:1.7
FROM node:20-bookworm-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN \
NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-ca.crt npm ci
COPY . .
CMD ["node", "server.js"]
#!/usr/bin/env bash
set -euo pipefail
docker build --secret id=corp_ca,src="$HOME/.config/corp-ca/corp-ca.pem" -t shop/api:dev .
required=false lets the same Dockerfile build outside the corporate network, where no CA file is provided.
- Trust it in running containers through the same mount-and-variables pattern used for local CAs:
x-corp-ca: &corp-ca
volumes:
- ${CORP_CA:-/dev/null}:/usr/local/share/ca-certificates/corp-ca.crt:ro
environment:
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/corp-ca.crt
REQUESTS_CA_BUNDLE: /etc/ssl/certs/ca-certificates.crt
SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt
HTTPS_PROXY: ${HTTPS_PROXY:-}
NO_PROXY: ${NO_PROXY:-localhost,127.0.0.1,.local,.localhost}
services:
api:
<<: *corp-ca
build: ./api
- Set proxy variables consistently, including
NO_PROXYfor local and internal hosts, so container-to-container traffic does not go through the corporate proxy.
Expected output
$ docker pull hello-world | tail -1
docker.io/library/hello-world:latest
$ docker build --secret id=corp_ca,src="$HOME/.config/corp-ca/corp-ca.pem" -t shop/api:dev . 2>&1 | grep -E 'npm ci|added'
#9 [4/5] RUN --mount=type=secret,id=corp_ca ... npm ci
#9 12.4 added 1412 packages in 11s
$ docker compose exec -T api node -e "fetch('https://registry.npmjs.org/left-pad').then(r => console.log(r.status))"
200
Pulls, builds and runtime HTTPS all succeed on the corporate network with verification enabled, and the same files build unchanged at home.
The CA never enters an image layer: builds read it from a secret mount, and running containers mount it from the host. Images pushed to the registry therefore contain no corporate-specific trust, which matters when the same images run in CI, in customer environments or in production, none of which should trust the office's inspection CA.
Prevention
Automate CA export in the bootstrap script and set
CORP_CAin.env, so new hires never meet the error.Detect interception in
make doctorby checking the issuer from the diagnostic; if it is the corporate CA andCORP_CAis unset, print the fix.Reject verification bypasses in code review —
strict-ssl=false,--trusted-host,verify=False,NODE_TLS_REJECT_UNAUTHORIZED=0— with a CI grep over config files and Dockerfiles.
Platform caveats
macOS (Docker Desktop): Docker Desktop reads the macOS keychain's trusted certificates when it starts; if pulls fail right after IT pushed a new CA, restart Docker Desktop.
WSL2: WSL distributions do not share the Windows certificate store. Export the CA from Windows (
certutil) and add it inside each distribution withupdate-ca-certificates.
Java tools (Gradle, Maven): import the CA into the JDK's
cacertswithkeytool -importcert -cacerts, or point the JVM at a combined truststore with-Djavax.net.ssl.trustStore.
Apple Silicon (ARM64): nothing architecture-specific; the same PEM works for every image.
Rollback
Remove the build secret, mounts and daemon CA; builds and pulls fail again on the corporate network but work elsewhere:
#!/usr/bin/env bash
set -euo pipefail
sudo rm -f /usr/local/share/ca-certificates/acme-corp-ca.crt && sudo update-ca-certificates --fresh
sudo systemctl restart docker
sed -i.bak '/^CORP_CA=/d' .env
Frequently Asked Questions
Why does everything work at home but fail in the office?
The office network intercepts HTTPS with a proxy that re-signs traffic using a corporate CA. Your OS trusts that CA, but Docker, containers and package managers keep their own trust stores that do not include it.
Is it acceptable to disable TLS verification behind the corporate proxy?
No. It makes clients accept any certificate, including from attackers outside the proxy, and the setting tends to leak into shared configuration. Distribute the corporate CA instead.
Should the corporate CA be baked into our images?
No. Provide it at build time through a BuildKit secret and at runtime through a mount, so images stay portable and do not trust a CA that only exists inside one company network.
How do I find the corporate CA's name?
Run openssl s_client against any public HTTPS site from the office network and read the issuer; that is the certificate to export from the OS store.