Skip to main content
Home/Blog/Fixing AI Coding CLIs Behind a Corporate Firewall (Proxy, TLS, Zscaler, CA Certs)
IT Operations

Fixing AI Coding CLIs Behind a Corporate Firewall (Proxy, TLS, Zscaler, CA Certs)

A definitive enterprise troubleshooting guide for getting Claude Code, Codex, Gemini, Antigravity, and Qwen working behind corporate proxies, TLS inspection, and custom CA certificates.

By Sean

You install Claude Code (or Codex, or Gemini CLI) on your work laptop, run it, and instead of generating code it spits out unable to get local issuer certificate or self-signed certificate in chain. Meanwhile curl https://api.anthropic.com works fine. If that sounds familiar, you're not dealing with a broken tool — you're dealing with your employer's network, and the same handful of fixes apply across every AI coding CLI on the market.

This is a troubleshooting guide for that exact situation: corporate proxies, TLS inspection (Zscaler, CrowdStrike Falcon, and friends), and custom CA certificates. The mechanics are shared, but each tool exposes them slightly differently, and one popular tool — Codex — needs an entirely separate set of knobs.

The three problems, in order

Almost every enterprise failure is one of three things:

  1. No route out. The CLI can't reach the internet because traffic must go through a proxy it doesn't know about. Symptoms: ETIMEDOUT, ECONNREFUSED, or hangs.
  2. TLS inspection. Your proxy decrypts HTTPS and re-signs it with a private root CA the CLI doesn't trust. Symptoms: unable to get local issuer certificate, SELF_SIGNED_CERT_IN_CHAIN, unable to verify the first certificate, UNABLE_TO_GET_ISSUER_CERT_LOCALLY.
  3. Auth or blocking. The proxy demands credentials (HTTP 407) or the firewall blocks a required domain (HTTP 403).

Diagnose in that order. There's no point fighting certificates if packets aren't leaving the machine.

Proxy configuration

Every one of these CLIs respects the standard proxy environment variables. Claude Code's docs are explicit about it, and the others follow the same convention:

export HTTPS_PROXY=https://proxy.example.com:8080
export HTTP_PROXY=http://proxy.example.com:8080
export NO_PROXY="localhost,127.0.0.1,.internal.example.com"

HTTPS_PROXY is the one that matters most — these tools talk HTTPS. For Claude Code, NO_PROXY accepts space- or comma-separated host lists, and NO_PROXY="*" bypasses the proxy entirely. Always include localhost and 127.0.0.1 in NO_PROXY, especially for tools that use a loopback OAuth callback (more on that below).

For a basic-auth proxy, put the credentials in the URL and URL-encode special characters:

# @ in the password becomes %40
export HTTPS_PROXY=http://username:p%40ssword@proxy.example.com:8080

A few hard limits worth knowing up front:

  • Claude Code does not support SOCKS proxies. Only HTTP/HTTPS. Don't waste time on a SOCKS5 endpoint.
  • NTLM/Kerberos proxies are not handled natively. If your proxy uses Windows Integrated Auth and returns 407, you need a local shim — cntlm or px — that does the NTLM handshake and exposes a no-auth local HTTP proxy. Point HTTPS_PROXY at that local port. Claude Code's official guidance for this case is to use an LLM gateway that supports your auth method.

TLS inspection and CA certificates

This is where most of the pain lives. When Zscaler or Falcon inspects TLS, it presents a certificate signed by your company's private root CA. The CLI has to trust that CA.

Here's the critical split: Node-based CLIs and Rust-based CLIs use different variables.

CLIRuntimeCA variableProxy
Claude CodeBunNODE_EXTRA_CA_CERTS, NODE_USE_SYSTEM_CA=1, CLAUDE_CODE_CERT_STOREHTTPS_PROXY (no SOCKS)
Codex CLIRust (reqwest)CODEX_CA_CERTIFICATE, falls back to SSL_CERT_FILEHTTPS_PROXY
Gemini CLINodeNODE_USE_SYSTEM_CA=1, then NODE_EXTRA_CA_CERTSHTTPS_PROXY
AntigravityNode (Electron)NODE_EXTRA_CA_CERTS + OS trust storeHTTPS_PROXY
Qwen CodeNodeNODE_EXTRA_CA_CERTSqwen --proxy <url> or settings.json

The single most common mistake: trying NODE_EXTRA_CA_CERTS on Codex. It's Rust, not Node, so the Node variables — including NODE_TLS_REJECT_UNAUTHORIZED=0 — are silently ignored. Use CODEX_CA_CERTIFICATE.

Getting your company's root CA

Ask your IT/security team for the corporate root CA in PEM format (.pem or .crt). If it's already installed in your OS trust store, you can often export it from there (Keychain Access on macOS, certmgr.msc on Windows). You want the root CA, not the leaf — a full PEM bundle is fine.

Claude Code

By default Claude Code trusts both its bundled Mozilla CA set and the OS certificate store, so if IT installed the Zscaler/Falcon root in your OS trust store, it should just work. CLAUDE_CODE_CERT_STORE controls which stores it uses (default bundled,system).

If the CA isn't in the OS store, point at a PEM file directly:

export NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.pem

Two gotchas specific to Claude Code:

  • WebFetch can still fail with unable to get local issuer certificate even when NODE_EXTRA_CA_CERTS is set correctly. Claude Code runs on Bun, and WebFetch's internal undici dispatcher doesn't inherit Bun's patched TLS context — so curl, node fetch, and openssl all succeed while WebFetch alone fails. The fix is export NODE_USE_SYSTEM_CA=1, which loads CAs from the OS/keychain store.
  • Set the variable in your shell, not just settings.json. NODE_EXTRA_CA_CERTS set inside ~/.claude/settings.json may not take effect for every code path; the actual process environment is more reliable.

Claude Code also supports mTLS / client-certificate auth if your proxy requires it, via CLAUDE_CODE_CLIENT_CERT, CLAUDE_CODE_CLIENT_KEY, and CLAUDE_CODE_CLIENT_KEY_PASSPHRASE (the passphrase only for encrypted keys).

Codex CLI

Set the CA before logging in — the same setting covers login, normal HTTPS requests, and secure WebSocket connections:

export CODEX_CA_CERTIFICATE=/path/to/corporate-root-ca.pem
codex login

Historically Codex failed OAuth token exchange behind inspecting proxies (Token exchange failed: error sending request for url https://auth.openai.com/oauth/token) because its reqwest client only trusted system default roots. CODEX_CA_CERTIFICATE (or SSL_CERT_FILE) is the fix for that. If CODEX_CA_CERTIFICATE is unset, Codex falls back to SSL_CERT_FILE.

Gemini CLI

Official guidance: try NODE_USE_SYSTEM_CA=1 first (it reads the OS native cert store), and if that doesn't resolve UNABLE_TO_GET_ISSUER_CERT_LOCALLY, add NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.crt.

Qwen Code

Node-based, so NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.crt handles the inspection CA. Errors you'll see include UNABLE_TO_GET_ISSUER_CERT_LOCALLY, UNABLE_TO_VERIFY_LEAF_SIGNATURE, and Device authorization flow failed: fetch failed. Proxy is set with the qwen --proxy <url> flag or the proxy key in settings.json.

Antigravity (Google)

Antigravity is the awkward one because it's a GUI app. You need the trifecta: HTTPS_PROXY/HTTP_PROXY/NO_PROXY (with localhost and 127.0.0.1 in NO_PROXY), NODE_EXTRA_CA_CERTS for the corporate CA, and the CA trusted in the OS store. On macOS:

sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain corporate-ca.pem

Critically, launch Antigravity from the same terminal that exported the variables — opening it from the Dock or Start Menu won't inherit them. Its OAuth sign-in can also break when endpoint protection blocks loopback traffic to the callback port (localhost:9004); proxy and http.proxyStrictSSL settings live in .antigravity/settings.json.

The "works in terminal, not in the app" trap

This deserves its own callout because it burns a lot of people. GUI apps started from the Dock/Start Menu don't inherit shell-exported env vars. So your NODE_EXTRA_CA_CERTS and HTTPS_PROXY reach the CLI but not the Electron app or a desktop "Code pane." Claude Desktop has documented bugs where it fails to forward NODE_EXTRA_CA_CERTS to the spawned Claude Code subprocess, producing Self-signed certificate detected errors. The durable fix for any GUI tool is to install the CA in the OS trust store rather than relying on env vars.

Verifying your setup

Before you start changing tool configs, confirm what's actually happening:

# Is the proxy even set?
env | grep -i proxy

# Does the proxy work for a known endpoint?
curl -x "$HTTPS_PROXY" https://api.anthropic.com

# What CA chain is the proxy presenting?
openssl s_client -connect api.anthropic.com:443 -showcerts

If curl and openssl succeed but the CLI fails, you've isolated it to the tool's CA handling — which means it's a NODE_USE_SYSTEM_CA / NODE_EXTRA_CA_CERTS / CODEX_CA_CERTIFICATE problem, not a network one.

Domains to allowlist

If you're hitting HTTP 403, get these added. For Claude Code, the required allowlist includes api.anthropic.com (API), claude.ai and platform.claude.com (auth), downloads.claude.ai (plugins/installer), bridge.claudeusercontent.com (Claude in Chrome WebSocket), and raw.githubusercontent.com (release notes). Codex auth needs auth.openai.com. The other tools have their own auth and API domains — capture them from a failing run and hand the list to your network team.

The escape hatch: a central AI gateway

Configuring five tools across a fleet of laptops doesn't scale. The alternative is to route every AI CLI through an internal LLM/AI gateway — Kong AI Gateway, WSO2, or LiteLLM — that handles the corporate proxy, auth, and CA termination centrally. This is the recommended path for NTLM/Kerberos environments where per-tool config is painful or impossible, and it gives security a single chokepoint to monitor. If your hesitation is partly about code leaving the building at all, a local-first option like Wide Area AI exposes an OpenAI-compatible endpoint that serves inference from your own hardware first and only fails over to cloud providers when local nodes are unavailable — you point each CLI's base URL at the gateway and keep most requests (and their per-token cost) on-premises.

Bottom line

  • Triage in order: route (ETIMEDOUT) → TLS (SELF_SIGNED_CERT_IN_CHAIN) → auth/block (407/403).
  • Proxy is universal: HTTPS_PROXY with URL-encoded creds for basic auth; a local cntlm/px shim for NTLM/Kerberos; no SOCKS for Claude Code.
  • CA certs split by runtime: Node CLIs (Claude Code, Gemini, Qwen, Antigravity) use NODE_USE_SYSTEM_CA=1 then NODE_EXTRA_CA_CERTS; Codex (Rust) uses CODEX_CA_CERTIFICATE / SSL_CERT_FILE.
  • Install the corporate CA in the OS trust store to fix GUI apps that ignore env vars.
  • Never leave NODE_TLS_REJECT_UNAUTHORIZED=0 on — debug-only, and useless for Codex anyway.
  • At scale, a central AI gateway beats configuring every tool on every machine.

Get the route, the CA, and the auth right and these tools behave identically to any other HTTPS client on your network — the failures just look scarier because the error strings are unfamiliar.

Frequently Asked Questions

Find answers to common questions

Your corporate TLS-inspection proxy (Zscaler, CrowdStrike Falcon, etc.) is decrypting HTTPS and re-signing it with a private root CA. curl works because it reads your OS trust store, where IT installed that CA. The CLI fails because, by default, it only trusts its own bundled CA set and does not see the corporate root. You fix it by pointing the tool at the corporate CA (NODE_EXTRA_CA_CERTS for Node-based CLIs, NODE_USE_SYSTEM_CA=1 to read the OS store, or CODEX_CA_CERTIFICATE for Codex).

NODE_EXTRA_CA_CERTS appends one PEM file of extra CAs on top of the bundled set. NODE_USE_SYSTEM_CA=1 tells the Node/Bun runtime to load CAs from the OS trust store, which is the most reliable fix when the corporate CA is already installed there. CLAUDE_CODE_CERT_STORE is Claude Code specific and controls which stores it consults (default 'bundled,system'). Try NODE_USE_SYSTEM_CA=1 first if your CA is in the OS store; fall back to NODE_EXTRA_CA_CERTS if not.

Codex CLI is Rust-based and uses the reqwest HTTP client, so Node.js environment variables like NODE_EXTRA_CA_CERTS and NODE_TLS_REJECT_UNAUTHORIZED=0 are simply ignored. Use CODEX_CA_CERTIFICATE pointing at your corporate root CA PEM (set it before running codex login). If CODEX_CA_CERTIFICATE is unset, Codex falls back to SSL_CERT_FILE.

Embed the credentials in the proxy URL: export HTTPS_PROXY=http://username:password@proxy.example.com:8080. Any special characters in the password must be URL-encoded — for example @ becomes %40. This handles basic-auth proxies. NTLM/Kerberos proxies need a different approach (a local auth-handling proxy or an LLM gateway).

Claude Code explicitly does not support SOCKS proxies — only HTTP/HTTPS proxies via HTTPS_PROXY/HTTP_PROXY. SOCKS5 support across the other CLIs is inconsistent and unreliable, so the safest assumption in an enterprise setting is to provide an HTTP/HTTPS proxy endpoint rather than SOCKS.

None of these CLIs handle Windows Integrated Auth (NTLM/Kerberos) natively. Run a local proxy that performs the NTLM/Kerberos handshake for you — cntlm or px — and expose a no-auth local HTTP endpoint, then point HTTPS_PROXY at that local port. Claude Code's official docs recommend an LLM gateway that supports your auth method for this scenario.

GUI apps launched from the Dock or Start Menu do not inherit shell-exported environment variables, so your HTTPS_PROXY and NODE_EXTRA_CA_CERTS never reach them. The fix is to install the corporate CA in the OS trust store (so the app picks it up regardless of env), or launch the app from a terminal that has the variables exported. Some desktop apps also have bugs where they fail to forward the CA env var to the spawned CLI subprocess.

No, not as a permanent fix. It disables TLS verification entirely, which defeats the protection against man-in-the-middle attacks and is a real security risk. Use it only for short-term debugging to confirm TLS is the problem, then remove it and configure the proper CA. It also does not work for Rust-based tools like Codex.

Let's turn this knowledge into action

Our experts can help you apply these insights to your specific situation. No sales pitch — just a technical conversation.