SSL, Proxies and Corporate Networks Blocking a Model API
10 min read · updated August 4, 2026
SSLCertVerificationError: certificate verify failed: unable to get local issuer certificate from a model API on a corporate network means, nine times in ten, that a TLS-inspecting proxy is re-issuing certificates with an internal certificate authority your runtime does not trust. The fix is to trust that authority, not to switch verification off.
Which layer failed
“Connection error” covers four distinct failures. Walk the ladder from the machine that is actually failing — the container, the CI runner, the laptop — and stop at the first rung that breaks.
# 1. DNS
getent hosts api.example-provider.com || nslookup api.example-provider.com
# 2. TCP
nc -vz api.example-provider.com 443 # or: timeout 5 bash -c \
# '</dev/tcp/api.example-provider.com/443'
# 3. TLS — and this is the one that tells you the most
openssl s_client -connect api.example-provider.com:443 \
-servername api.example-provider.com </dev/null 2>&1 | head -25
# 4. HTTP
curl -sS -o /dev/null -w '%{http_code}\n' https://api.example-provider.com/v1/modelsThe third command is the diagnostic. Look at the issuer line of the certificate chain it prints. If the issuer is a public certificate authority, TLS is fine and your problem is elsewhere. If the issuer is your employer — something like CN = Acme Corp Root CA — then every TLS connection you make is being decrypted and re-encrypted, and that is both the cause and the shape of the fix.
Reading the exception you got
| Error | Description |
|---|---|
| CERTIFICATE_VERIFY_FAILED / unable to get local issuer certificate | The chain presented does not lead to a root the runtime trusts. Inspecting proxy, or a trust store missing entirely (common in slim containers). |
| SELF_SIGNED_CERT_IN_CHAIN / DEPTH_ZERO_SELF_SIGNED_CERT | Same family. A proxy or an appliance is terminating TLS with its own certificate. |
| CERTIFICATE_VERIFY_FAILED: certificate has expired / is not yet valid | Either a genuinely expired certificate, or — far more often on a container or a VM — the local clock is wrong. |
| ECONNREFUSED | Something answered and said no. A local proxy that is not running, or a wrong port. Nothing to do with certificates. |
| ETIMEDOUT / connection timed out | Packets went nowhere. Egress firewall, a missing route, or an IPv6 address that is advertised and blackholed. |
| EAI_AGAIN / Name or service not known | DNS. In a container, usually the resolver configuration rather than the network. |
| APIConnectionError / ConnectError | An SDK wrapper around one of the above. Print the __cause__ or the nested error; the wrapper hides the diagnosis. |
The last row matters more than it looks. Most SDK-level connection errors wrap the real exception, and the real exception is the one that names the layer. In Python, e.__cause__; in Node, the cause property.
The inspecting proxy, and the right fix
A TLS-inspecting proxy sits in the middle of every HTTPS connection, decrypts it, inspects it, and re-encrypts it with a certificate signed by an internal CA. Browsers work because the corporate CA was pushed into the operating system trust store by device management. Language runtimes frequently do not, because several of them ship their own trust store and ignore the system one entirely — Python’s certifi bundle and Node’s built-in root list are both like this.
- Get the CA certificate in PEM form. Your IT department has it. Failing that, extract the top of the chain from the
openssl s_clientoutput above. Save it somewhere readable by the process, not in the repository. - Point each runtime at it. The variables differ and setting only one is why this often half-works:
export SSL_CERT_FILE=/etc/ssl/certs/corp-ca.pem # OpenSSL, many tools export REQUESTS_CA_BUNDLE=/etc/ssl/certs/corp-ca.pem # Python requests export CURL_CA_BUNDLE=/etc/ssl/certs/corp-ca.pem # curl export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/corp-ca.pem # Node: ADDS to the # built-in roots export AWS_CA_BUNDLE=/etc/ssl/certs/corp-ca.pem # AWS SDKsNODE_EXTRA_CA_CERTSis additive, which is what you want. Note that it is read once at process start, so a running server will not pick it up. - Or install it into the system store, which is better for containers.
COPY corp-ca.crt /usr/local/share/ca-certificates/corp-ca.crt RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates && update-ca-certificates && rm -rf /var/lib/apt/lists/*This fixes every tool in the image at once rather than each library separately. - Verify against the bundle before changing application code.
curl --cacert /etc/ssl/certs/corp-ca.pem \ https://api.example-provider.com/v1/models python -c "import certifi; print(certifi.where())" # where Python looks
verify=False or NODE_TLS_REJECT_UNAUTHORIZED=0. They do not fix the problem, they remove the check that detects it, and they do it for every connection the process makes — including the one carrying your API key. If you use one to confirm a diagnosis in a scratch shell, delete it in the same sitting. A repository grep for these two strings is a worthwhile five minutes on any codebase that has been through a corporate network.Proxy environment variables per runtime
A forward proxy is a separate issue from certificate inspection, though the same networks usually have both. The conventional variables are widely but not universally honoured, and the exceptions cause real confusion.
export HTTPS_PROXY=http://proxy.corp:8080 export HTTP_PROXY=http://proxy.corp:8080 export NO_PROXY=localhost,127.0.0.1,.internal.corp,169.254.169.254
- Python.
requestsandhttpxread these, in both upper and lower case. Note that lower-casehttp_proxyis preferred by some libraries for historical reasons — setting both cases removes the question. - Node. The built-in
fetchdoes not readHTTPS_PROXY. This surprises almost everyone. You need an explicit dispatcher — undici’sProxyAgentwithsetGlobalDispatcher, or an equivalent agent configured on the client. A Python service and a Node service on the same host behaving differently is this, every time. NO_PROXYmatters more than it seems. Sending internal traffic through an external proxy breaks service discovery, metadata endpoints and health checks. Cloud instance metadata addresses in particular must be excluded or instance credentials stop resolving.- Authenticated proxies. Credentials go in the URL as
http://user:[email protected]:8080, which means the proxy password is now in your environment and will end up in a log or a crash dump. Prefer a proxy that authenticates the host rather than the request where you can.
Containers, DNS and the slow failures
- No CA bundle at all. Minimal base images — some Alpine variants, scratch, distroless without the certificate layer — ship no roots. Every HTTPS call fails identically, and the fix is installing
ca-certificates. The tell is that it fails for every host including well-known public ones. - Clock skew. A suspended VM or a container on a host with a drifting clock produces “certificate is not yet valid”. Check
date -ubefore believing anything about certificates. - IPv6 that is advertised and blackholed. The resolver returns an AAAA record, the connection attempt goes nowhere, and the client waits for a timeout before trying IPv4. This presents as a request that takes exactly 20 or 75 seconds and then either succeeds or fails — a suspiciously round number is the signature. Happy-eyeballs behaviour differs by client; disabling IPv6 for the container is the blunt fix.
- Egress allowlists by hostname. Firewalls that allowlist by SNI break when a provider adds a new hostname for a new endpoint, so a previously working integration fails on one route only. The signature is that some endpoints work and others time out.
- Intermittent failure under concurrency. If it works at one request per second and fails at fifty, look at connection pool limits and ephemeral port exhaustion rather than at TLS. That one belongs with latency spikes, which shares most of its diagnostics with this page.
Finally, distinguish a network failure from a service failure before escalating to a provider. A 5xx means you reached them; 502, 503 and 504 covers that side. A TLS or DNS error means you did not, and no status page will explain it.