Claude Code Behind a Corporate Proxy: Why NODE_EXTRA_CA_CERTS Isn't the Fix
Unable to get local issuer certificate behind Zscaler or any TLS-inspecting proxy. Claude Code already trusts your OS certificate store by default — so the usual advice fixes it for some people and not others. The variable that actually decides is CLAUDE_CODE_CERT_STORE, and whether your runtime can read the OS store at all.
If you're getting unable to get local issuer certificate or SELF_SIGNED_CERT_IN_CHAIN behind Zscaler, Netskope or any TLS-inspecting proxy, the advice you'll find everywhere is to set NODE_EXTRA_CA_CERTS. Every guide to the Claude Code SSL certificate error opens with it. That works for some people and not others, and the inconsistency is the actual clue.
Claude Code already trusts your operating system's certificate store by default. From the enterprise network docs:
Enterprise TLS-inspection proxies work without additional configuration when their root certificate is installed in the OS trust store and the runtime can read it.
So if your corporate CA certificate is already in the OS store and the SSL error persists, adding a .pem path is treating a symptom. Start with that second clause instead — and the runtime can read it.
The one sentence that explains the inconsistency
Reading the OS store requires a runtime that exposes tls.getCACertificates:
| Install method | Can read the OS trust store |
|---|---|
| Native installer | Always |
| npm install | Only on Node 22.15 or later |
On an older Node, only the bundled Mozilla set and NODE_EXTRA_CA_CERTS apply. That single line explains why the same fix works for your colleague and not for you: they're on the native installer, you're on an npm install with Node 20.
Check your runtime before changing any certificate setting:
node --version # npm installs: needs >= 22.15 to read the OS store
claude doctorIf you're on an npm install with an older Node, you have two real options — upgrade Node, or keep using NODE_EXTRA_CA_CERTS, which genuinely is the right tool in that situation.
The variable that actually decides: CLAUDE_CODE_CERT_STORE
This one is barely mentioned anywhere, and it's the switch that controls the whole behaviour. It takes a comma-separated list of sources:
| Value | Means |
|---|---|
bundled | The Mozilla CA set shipped with Claude Code |
system | Your operating system trust store |
The default is bundled,system — both. To trust only one:
export CLAUDE_CODE_CERT_STORE=system # OS store only
export CLAUDE_CODE_CERT_STORE=bundled # bundled Mozilla set only⚠️ It has no dedicated settings.json schema key. Set it in the env block of ~/.claude/settings.json or directly in the process environment. Putting it at the top level of a settings file does nothing, silently.
If your corporate CA is in the OS store and the SSL failure persists, check that nothing has narrowed this to bundled — that alone will reject every corporate certificate your proxy presents.
Verify the certificate loaded — /status will lie to you
This is the trap that turns a ten-minute certificate fix into an afternoon.
/status shows an Additional CA cert(s) row with your NODE_EXTRA_CA_CERTS path. It shows the path without checking the file loaded. A typo'd path, a permissions problem, a malformed PEM — all look identical to success.
Use the debug log instead:
claude --debugOutput goes to ~/.claude/debug/<session-id>.txt, not the terminal. Look for these:
CA certs: Appended extra certificates from NODE_EXTRA_CA_CERTS (/etc/ssl/certs/corp-ca.pem)
mTLS: Loaded client certificate from CLAUDE_CODE_CLIENT_CERT
mTLS: Loaded client key from CLAUDE_CODE_CLIENT_KEYIf a file couldn't be read, you get a Failed to read or Failed to load line with the reason. That's the difference between guessing and knowing.
The /status rows for mTLS client cert and mTLS client key are more honest — they appear only when the files loaded, so a missing row means the load failed.
Proxy configuration, and the parts that surprise people
export HTTPS_PROXY=https://proxy.example.com:8080
export NO_PROXY="localhost,192.168.1.1,.example.com"Four things worth knowing:
- Precedence is fixed. Claude Code uses the first one set, in the order
https_proxy,HTTPS_PROXY,http_proxy,HTTP_PROXY. Lowercase variants work. If you set two and get the wrong one, that's why. - SOCKS proxies are not supported. At all.
- You don't need a loopback entry in
NO_PROXY. WebSocket connections tolocalhost,::1and127.0.0.0/8never go through the proxy. - The proxy URL is the one setting validated at startup. An unparseable value — one missing the
http://scheme, say — stops launch with an error naming the variable. Everything else on this page fails later, on a request.
And the timing rule underneath all of it: variables are read once at startup. A running session doesn't pick up changes to your shell environment, so export then retry in the same session proves nothing.
Why your settings work in the terminal but not the Desktop Code tab
This one produces a genuinely confusing bug report, and it's deliberate behaviour rather than a fault.
When the Desktop app manages the provider connection, Claude Code reads NODE_EXTRA_CA_CERTS, the mTLS variables and HTTP_PROXY/HTTPS_PROXY/NO_PROXY only from managed settings and ~/.claude/settings.json. It ignores them in a repository's own settings files — so a checked-out repo can't redirect the TLS or proxy path of a session whose credentials come from the app.
That's a sensible security boundary: otherwise cloning a repository could reroute your authenticated traffic. But if you put your CA config in a project's .claude/settings.json, it will work in your terminal and silently not in the Desktop Code tab. Related: issue #22559.
A local, SSH or WSL Code tab session signed in through claude.ai isn't app-managed, so it reads every settings scope like a normal terminal session.
Cloud sessions ignore these entirely — the hosting environment owns the connection. NODE_EXTRA_CA_CERTS, NODE_TLS_REJECT_UNAUTHORIZED and the client-certificate variables are all dropped, and each ignored key is noted in the session's debug log.
Background agents don't inherit your shell
If you use claude agents, --bg or /background, this will bite you eventually.
Background sessions run under a per-user supervisor process that outlives your shell. It inherits the environment of whichever shell started it first — and an OS-installed supervisor gets no shell environment at all.
So exporting your proxy or CA variables in .bashrc reaches background agents when that shell happened to cold-start the supervisor, and silently doesn't when a different one did. Same machine, same config, non-deterministic result.
Put them in the env block of ~/.claude/settings.json instead. Settings are the only configuration that reaches every background session:
{
"env": {
"HTTPS_PROXY": "https://proxy.example.com:8080",
"NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/corp-ca.pem",
"CLAUDE_CODE_CERT_STORE": "bundled,system"
}
}An already-running supervisor keeps the configuration it started with, so run claude daemon stop --any after changing this.
Client certificates, if your gateway requires them
export CLAUDE_CODE_CLIENT_CERT=/path/to/client-cert.pem
export CLAUDE_CODE_CLIENT_KEY=/path/to/client-key.pem
export CLAUDE_CODE_CLIENT_KEY_PASSPHRASE="your-passphrase"Rotation is handled better than most people expect: replace the files at the same paths and a running session picks them up. Since v2.1.232, a connection-level failure — a reset or a TLS handshake error — triggers a re-read and a retry with the new pair.
Two caveats. Claude Code re-reads in response to failures, not by watching files, so nothing happens at the moment you swap them. And if the gateway completes the handshake and returns an HTTP error instead of resetting, there's no re-read — you wait for the next settings apply or a restart.
Triage order
- Is your corporate root in the OS trust store? If yes, this should already work — go to step 2 rather than adding a
.pem. - Can your runtime read the OS store? Native installer, or npm on Node ≥ 22.15. If not, that's your SSL error explained.
- Check nothing narrowed
CLAUDE_CODE_CERT_STOREaway frombundled,system. - Set
NODE_EXTRA_CA_CERTSonly if 2 rules out the OS store — then confirm in the debug log, not/status. - If it works in the terminal but not the Desktop Code tab, move the config to
~/.claude/settings.json. - If it works interactively but not for background agents, move it to the
envblock andclaude daemon stop --any.
Key takeaways
- Claude Code trusts your OS certificate store by default — CLAUDE_CODE_CERT_STORE defaults to bundled,system.
- Reading the OS store needs tls.getCACertificates: the native installer always has it, npm installs need Node 22.15+. That is why the same fix works for one person and not another.
- /status shows the NODE_EXTRA_CA_CERTS path without checking the file loaded. Confirm in claude --debug output instead.
- CLAUDE_CODE_CERT_STORE has no settings.json schema key — it must go in the env block or the process environment.
- Background agents run under a supervisor that inherits whichever shell started it first, so shell exports reach them unreliably. Use the env block in settings.
Frequently asked questions
Why does NODE_EXTRA_CA_CERTS work for some people and not others?
Because it is usually not the thing that matters. Claude Code trusts both its bundled Mozilla CA set and your operating system trust store by default, so if your corporate root is installed in the OS store it should already work. Reading the OS store needs a runtime with tls.getCACertificates — the native installer always has it, but npm installs need Node 22.15 or later. On an older Node only the bundled set and NODE_EXTRA_CA_CERTS apply, which is why the same advice fixes it for one person and not the next.
What is CLAUDE_CODE_CERT_STORE and what is the default?
It takes a comma-separated list of certificate sources. The recognized values are bundled, meaning the Mozilla CA set shipped with Claude Code, and system, meaning your operating system trust store. The default is bundled,system. It has no dedicated settings.json schema key, so set it in the env block of your settings file or directly in the process environment.
Why did my certificate settings stop applying in the Claude Desktop Code tab?
When the Desktop app manages the provider connection, Claude Code reads the TLS and proxy variables only from managed settings and ~/.claude/settings.json. It deliberately ignores them in a repository's own settings files, so a checked-out repo cannot redirect the TLS or proxy path of a session whose credentials come from the app.
How do I confirm my CA certificate actually loaded?
Run claude --debug and read ~/.claude/debug/<session-id>.txt for a line reading 'CA certs: Appended extra certificates from NODE_EXTRA_CA_CERTS'. Do not rely on /status alone — its Additional CA cert(s) row shows the configured path without checking that the file loaded, so it looks identical whether the load succeeded or failed.
The reason the Claude Code SSL certificate error burns so much time in enterprises isn't that it's hard — it's that the default behaviour is better than people expect, so everyone reaches for the override first and then debugs the override. Establish whether the OS store is being read at all, and most of these tickets close in ten minutes.
If the failure is at startup rather than on a request, the error text usually names a different cause entirely — Claude Code won't start on Windows separates the five that get confused for each other. And for the transient failures that look like network problems but aren't, 529 overloaded and what gets retried covers where the retry boundary sits. Controlling what these tools carry into a session is the problem ContextZero came out of.
References
- Enterprise network configuration — Claude Code docscode.claude.com · accessed 2026-09-06
- Environment variables reference — Claude Code docscode.claude.com · accessed 2026-09-06
- Desktop app does not forward NODE_EXTRA_CA_CERTS to the CLI subprocess (issue #22559)github.com · accessed 2026-09-06
Last reviewed September 6, 2026
Tahir Nazir
Senior AI Engineer & Full-Stack Lead
5+ years shipping AI-powered products — RAG pipelines, agentic workflows, and MCP tooling. Top Rated on Upwork with a 100% job success score.
More about Tahir →Keep reading
New posts land here first. Follow along by RSS, or get in touch if you are building something similar.
Related articles
Claude Code Won't Start on Windows? Match the Error, Not the Guide
Raw mode is not supported, 'claude' is not recognized, 32-bit Windows, Exec format error on WSL1 — five different Windows startup failures that get treated as one problem. Each has a distinct cause and a distinct fix, and four of them aren't install failures at all.TroubleshootingAI Engineering8 min readClaude Code 529 Overloaded: What It Retries For You, and What It Won't
A 529 isn't your usage limit and doesn't touch your quota. Claude Code already retried ten times before it told you. The useful question is which failures it retries automatically, which it deliberately doesn't, and the one environment variable that stops an unattended run dying on a transient capacity blip.TroubleshootingAI Engineering8 min readClaude Code on Vertex AI: Why You Get 404s, Wrong Regions and an Opus-Sized Bill
Model not found on Google Cloud's Agent Platform is rarely one problem. A malformed region is silently ignored and falls back to us-east5, ANTHROPIC_VERTEX_PROJECT_ID overrides the project in your credentials, and a deployment with no pinned model is billed at the Opus rate.TroubleshootingAI Engineering10 min read