How do you fix certificate errors behind a TLS-inspecting proxy?
A SELF_SIGNED_CERT_IN_CHAIN or unable to get local issuer certificate error from Claude Code, Cursor, Codex, or GitHub Copilot means that a TLS-inspecting proxy, such as Zscaler or Netskope, re-signed the connection with a corporate root certificate that the tool doesn't trust. Fix it by exporting that root and giving it to each tool: the operating system trust store for Claude Code and Copilot, NODE_EXTRA_CA_CERTS for Node.js tools, CODEX_CA_CERTIFICATE for Codex, and a combined bundle for git, npm, and Python. Cursor asks you to exclude its domains from inspection instead. Never set NODE_TLS_REJECT_UNAUTHORIZED=0.
The following procedure applies to every tool on this page:
- Confirm that inspection is the cause by reading the certificate chain the tool receives.
- Export the corporate root certificate as a PEM file, and build a combined bundle of public roots plus that root.
- Check that your MDM installed the root in the operating system trust store as trusted.
- Point each tool that ignores the OS store at the root or the bundle, using the settings in the following sections.
- Restart the tool and confirm that its debug output or a test request succeeds.
Commands and settings come from each vendor's documentation, reviewed on November 3, 2026. Error strings also come from issue trackers and from a test server with a private root.
What do the certificate error messages mean?
The error text depends on the TLS library inside the tool, and on whether the proxy sends its root certificate in the chain. The following table maps each message to its cause.
| Message | Where you see it | What it means |
|---|---|---|
| Claude Code, npm, other Node.js tools, curl, and Python | The proxy sent its own root certificate in the chain, and the tool doesn't trust that root |
| Claude Code, Cursor, Node.js tools, curl, git, and Python | The proxy sent a certificate and its issuing CA, and the tool can't find the root that signed the issuing CA |
CERTIFICATE_VERIFY_FAILED | pip, Azure CLI, and other Python tools | Python's prefix for a failed check, followed by one of the two preceding reasons |
unable to verify the first certificate | GitHub Copilot and Node.js tools | The proxy sent only the site certificate, with no issuing CA, and the tool can't build a chain |
Claude Code prints the OpenSSL code in its message. The following lines are the two forms in its error reference:
Unable to connect to API: SSL certificate verification failed (UNABLE_TO_GET_ISSUER_CERT_LOCALLY). The certificate comes from an authority Claude Code doesn't trust, usually a TLS-inspecting corporate proxy or a gateway signed by a private CA: set NODE_EXTRA_CA_CERTS to that CA bundle, or add it to the system certificate store · see https://code.claude.com/docs/en/network-configUnable to connect to API: Self-signed certificate detected (SELF_SIGNED_CERT_IN_CHAIN). The certificate comes from an authority Claude Code doesn't trust, usually a TLS-inspecting corporate proxy or a gateway signed by a private CA: set NODE_EXTRA_CA_CERTS to that CA bundle, or add it to the system certificate store · see https://code.claude.com/docs/en/network-configVersions before 2.1.273 end both messages at Check your proxy or corporate SSL certificates. Cursor reports [internal] unable to get local issuer certificate in chat, as in this Cursor forum thread from July 2, 2026. Python prints [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate, and git built on OpenSSL prints curl's SSL certificate problem: unable to get local issuer certificate.
To confirm that a proxy is the cause, read the chain that your machine receives for the host the tool calls:
openssl s_client -connect api.anthropic.com:443 \ -servername api.anthropic.com -showcerts </dev/null 2>/dev/null | grep -E ' s:| i:'Each certificate prints as an s: subject line and an i: issuer line. If an issuer names your security vendor or your company, the connection is inspected, and the last i: line names the root that you need to export. Behind an explicit proxy, add -proxy HOST:PORT. Cursor documents a similar check, curl -v https://api2.cursor.sh |& grep -C1 issuer:, which shows Amazon RSA when the connection isn't inspected.
How do you export the corporate root certificate?
Most tools want a PEM file: Base64 text between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- lines. MDM usually installed the root already, so export it from the system store.
On macOS, security find-certificate with -p writes PEM (ss64 reference). MDM profiles put roots in the System keychain:
mkdir -p "$HOME/certs"security find-certificate -a -c "ROOT_CA_NAME" -p \ /Library/Keychains/System.keychain > "$HOME/certs/corp-root.pem"openssl x509 -in "$HOME/certs/corp-root.pem" -noout -subject -issuer -enddateReplace ROOT_CA_NAME with part of the root's common name from the last i: line of the openssl s_client output. For a root, the subject= and issuer= lines match. The -a flag exports every match, so check that the file holds only the certificates you expect.
On Windows, PowerShell's Cert: drive reads the local machine's trusted root store, including roots that Group Policy deployed. Export-Certificate writes one certificate as a DER .cer file by default (Microsoft Learn), and certutil -encode converts it to Base64 PEM:
New-Item -ItemType Directory -Force "$HOME\certs" | Out-Null$cert = Get-ChildItem Cert:\LocalMachine\Root | Where-Object Subject -like "*ROOT_CA_NAME*" | Select-Object -First 1Export-Certificate -Cert $cert -FilePath "$HOME\certs\corp-root.cer"certutil -encode "$HOME\certs\corp-root.cer" "$HOME\certs\corp-root.pem"From a Command Prompt, certutil -store Root "ROOT_CA_NAME" corp-root.cer writes the matching certificate in one step, because -store takes a common name and an output file (certutil reference). If it finds nothing, add -enterprise or -grouppolicy.
Some tools replace their whole trust store with the file you give them, so a file that holds only the corporate root breaks every destination your proxy doesn't inspect, such as a package registry on the bypass list. For those tools, build a combined bundle. On macOS, start from Apple's system roots:
security find-certificate -a -p \ /System/Library/Keychains/SystemRootCertificates.keychain \ > "$HOME/certs/ca-bundle.pem"cat "$HOME/certs/corp-root.pem" >> "$HOME/certs/ca-bundle.pem"On any system with Python's certifi package, cat "$(python3 -m certifi)" "$HOME/certs/corp-root.pem" > "$HOME/certs/ca-bundle.pem" builds the same file from Mozilla's roots. The rest of this guide calls the two files corp-root.pem and ca-bundle.pem.
How do you fix Claude Code certificate errors?
Claude Code trusts its bundled Mozilla roots and the operating system's store by default, controlled by CLAUDE_CODE_CERT_STORE, whose default is bundled,system. Reading the OS store needs a runtime with tls.getCACertificates: the native installer always has one, and an npm install needs Node.js 22.15 or later. On older Node.js versions, Claude Code trusts only the bundled roots and NODE_EXTRA_CA_CERTS (Claude Code network configuration). On a managed laptop where MDM installed the root, the native build connects without changes.
If the error persists, run claude doctor to see how Claude Code is installed, and node --version if it's an npm install. Then point Claude Code at the root:
export NODE_EXTRA_CA_CERTS="$HOME/certs/corp-root.pem"NODE_EXTRA_CA_CERTS adds certificates to the defaults, so the root alone is enough. Node.js reads it only when the process starts (Node.js CLI reference), so restart Claude Code after you change it.
A shell profile such as ~/.zshrc works for claude in a terminal but misses two cases. Background agents run under a supervisor that inherits the environment of whichever shell started it first, and Claude Desktop sessions in which the app manages the connection read NODE_EXTRA_CA_CERTS only from managed settings and ~/.claude/settings.json. Anthropic recommends the env block of settings instead, and in most sessions a value there overrides a shell export (settings reference). For one developer, put it in ~/.claude/settings.json. For a fleet, deliver it in /Library/Application Support/ClaudeCode/managed-settings.json on macOS or C:\Program Files\ClaudeCode\managed-settings.json on Windows (managed settings):
{ "env": { "NODE_EXTRA_CA_CERTS": "/Library/Application Support/ExampleCorp/corp-root.pem" }}Use an absolute path that exists on every device, and deploy the PEM file through the same MDM channel. To confirm it loaded, start claude --debug and look in ~/.claude/debug/ for the line CA certs: Appended extra certificates from NODE_EXTRA_CA_CERTS. The additional CA certificates row in /status shows only the configured path, not whether the file loaded. If requests fail after the certificate is trusted, some inspection proxies mishandle compressed request bodies, and Anthropic documents CLAUDE_CODE_GZIP_REQUEST_BODIES=0 for that case.
How do you fix Cursor certificate and streaming errors?
Cursor's enterprise network guide recommends that you turn off SSL inspection for .cursor.sh, cursor.com, *.cursor.com, cursor-cdn.com, marketplace.cursorapi.com, both *.cursorvm.com and *.*.cursorvm.com, and four more hosts that the guide lists. It says inspection, secure web gateways, and DLP "often" cause timeouts and errors in Agent. If policy requires inspection on all traffic, the proxy has to pass server-sent events through without buffering.
Streaming fails separately from trust. Cursor uses HTTP/2 bidirectional streaming and falls back to HTTP/1.1 server-sent events when HTTP/2 doesn't work. When the fallback doesn't happen, Cursor's network troubleshooting page says some corporate proxies, naming Zscaler, block HTTP/2, and tells you to go to Cursor Settings > Network, set HTTP Compatibility Mode to HTTP/1.1, and restart Cursor. The same pane has Run Diagnostics. HTTP/1.1 doesn't fix a certificate error.
For the certificate error itself, Cursor is a fork of VS Code and keeps VS Code's http.systemCertificates setting, which loads CA certificates from the OS and defaults to true (VS Code source). In the forum thread cited earlier, the fix was to turn on http.experimental.systemCertificatesV2 in the VS Code settings, keep http.systemCertificates on, and quit and reopen Cursor fully, because reloading the window isn't enough. On Windows, the root has to be in Trusted Root Certification Authorities, not only the personal store. Tools in Cursor's terminal, such as az or pip, need the fixes for general developer tools.
How do you fix Codex CLI certificate errors?
Codex reads its trust anchors from CODEX_CA_CERTIFICATE, a path to a PEM bundle, and falls back to SSL_CERT_FILE when that variable is unset. The setting applies to login, normal HTTPS requests, and secure WebSocket connections, and OpenAI's authentication docs tell you to set it before you sign in:
export CODEX_CA_CERTIFICATE="$HOME/certs/ca-bundle.pem"codex loginThe docs describe the file as a CA bundle and don't say that it adds to the default roots, so point it at the combined bundle rather than the corporate root alone. Codex doesn't document NODE_EXTRA_CA_CERTS, so the variable that fixes Claude Code doesn't fix Codex. Put the export in the shell profile that starts codex, or in the environment that your MDM sets for developer shells.
How do you fix GitHub Copilot certificate errors?
GitHub's network settings page says Copilot reads certificates from the operating system's trust store and extra certificates from NODE_EXTRA_CA_CERTS. Its troubleshooting page lists the errors certificate signature failure, custom certificate, and unable to verify the first certificate, and names the lookup mechanism per platform: the win-ca package on Windows, mac-ca on macOS, and /etc/ssl/certs/ca-certificates.crt or /etc/ssl/certs/ca-bundle.crt on Linux. To test the path that Copilot Chat uses, run curl --verbose https://api.githubcopilot.com/_ping, which returns 200 on success.
In VS Code, two settings matter. http.systemCertificates defaults to true and loads OS certificates for extensions. http.proxyStrictSSL also defaults to true and verifies the proxy's certificate against the trusted CAs. GitHub's docs mention clearing Proxy Strict SSL to ignore certificate errors and warn that doing so "can cause security issues and is not recommended". Leave it selected and fix the trust store.
The Copilot CLI documentation doesn't describe a certificate setting. The tracking issue, copilot-cli#333, reports fetch failed and unable to get issuer certificate behind SSL inspection. On October 26, 2026, a maintainer replied that fixes had shipped, most recently in v1.0.83, and asked reporters to retest on v1.0.103. Update the CLI and keep the root in the OS store.
How do you fix git, npm, and pip behind the same proxy?
Zscaler's post on SSL inspection in developer environments (September 20, 2024) gives a per-tool command for git, npm, pip, Python libraries, and Java, because "some applications use their own application trust store instead of using the default system store." The following table lists the setting for each tool.
| Tool | Setting | What to point it at |
|---|---|---|
| git |
| The combined bundle, because the file replaces git's CA bundle. The
|
| npm | cafile | The combined bundle, because a configured CA makes npm trust only that authority. |
| pip |
| Often nothing: pip 24.2 and later use system certificates in addition to certifi. |
| Python Requests | REQUESTS_CA_BUNDLE | The combined bundle, because it overrides the certifi bundle. |
| Python standard library | SSL_CERT_FILE | The combined bundle. Python's |
Sources: git-config, npm config, pip HTTPS certificates, and Requests advanced usage. The following commands apply the git and npm settings:
git config --global http.sslCAInfo "$HOME/certs/ca-bundle.pem"npm config set cafile "$HOME/certs/ca-bundle.pem"The tempting shortcut is NODE_TLS_REJECT_UNAUTHORIZED=0. Node.js documents that the value 0 turns off certificate validation for TLS connections, "makes TLS, and HTTPS by extension, insecure," and that its use is "strongly discouraged" (Node.js CLI reference). Anthropic's error reference also says not to set it. It switches off verification for every connection the process makes, not only the inspected one, so on a café network an on-path attacker can present a certificate for api.anthropic.com and read the OAuth token and source code in each request. A variable set in Claude Code's env block also reaches every subprocess that the agent starts, including npm install and MCP servers. The same reasoning applies to clearing http.proxyStrictSSL, git config http.sslVerify false, and curl --insecure.
Why do developer tools keep breaking under TLS inspection?
Browsers use the operating system's trust store, so when MDM installs the inspection root, Chrome and Safari keep working on day one. Developer tools bring their own. Node.js bundles Mozilla's root list and reads the system store only when you pass --use-system-ca or NODE_USE_SYSTEM_CA=1, Requests uses certifi, git uses whatever CA file its TLS library points to, and Java has cacerts. A coding agent sits on top of several of these at once. Claude Code's Bash tool runs git, npm, and pip, and each of those subprocesses applies its own rules.
Some clients pin certificates and refuse any root that isn't theirs. Netskope maintains a bypass list of apps that use certificate pinning and offers "Certificate-Pinned Applications" as a steering exception. Cursor's network guide lists certificate pinning for critical services, and the ChatGPT desktop app for macOS needs an MDM profile that adds the inspection root's hash to its pinned list (dope.security vs Arcjet). For the mechanism, see certificate pinning and inspection bypass lists.
Agents also stream. Claude Code, Cursor, and Codex hold long-lived streaming responses or WebSocket connections, and an inspection engine that buffers responses or doesn't speak HTTP/2 breaks them even after the certificate is trusted.
Each fix has an operational cost. A security team that keeps inspection on maintains a domain list per tool, and those lists change: Cursor's includes a double wildcard, *.*.cursorvm.com, and accounts.x.ai, and Claude Code's network page lists more than a dozen hosts. Each developer machine needs a root file and a setting per tool, and shell profiles don't carry those settings to GUI apps or background agents. Bypassing a domain is one change on the gateway, so each support ticket tends to end with a new bypass entry.
Why do inspection bypasses create shadow AI?
Every bypass entry turns a destination into a hostname. The gateway still logs that a device reached api2.cursor.sh, but it no longer sees the request, the user agent, or the account. For AI tools, it also loses the one control that separates personal from company accounts: the tenant restriction headers for Claude and ChatGPT need TLS decryption on those domains, as described in blocking personal AI accounts on work laptops. The following table shows what a bypass costs for each tool.
| Tool | Recommended fix | What a bypass costs |
|---|---|---|
| Claude Code | Trust the root in the OS store or with | The |
| Codex and ChatGPT | Trust the bundle with | The |
| Cursor | Exclude Cursor's domains, as Cursor recommends | Content inspection only; the account control is the
|
| GitHub Copilot | Trust the root in the OS store | Content inspection only; plan hostnames separate individual from business plans without decryption |
The second effect is behavioral. A developer who can't get the approved agent working behind the proxy doesn't stop using agents. They tether to a phone hotspot, which skips the corporate resolver and gateway, run the agent on a personal laptop with the repository cloned there, or sign in with a personal Claude Max plan from home. Each workaround moves the work outside your gateway, your MDM, or both, and none of them shows up as an incident. Publishing the root file and the per-tool settings before developers hit the error removes the reason to route around the proxy, which makes the certificate rollout part of your shadow AI program.
How do you discover and govern AI tools without decrypting TLS?
Several sources see AI use without a root certificate on the device: resolver logs, OAuth grants, expense data, MDM inventory, config files, and Microsoft Defender for Endpoint's network events. Each method's gaps are in how to detect shadow AI, and endpoint vs network shadow AI discovery compares the approaches.
Arcjet is one of these options. The Arcjet endpoint agent for macOS and Windows reads the destination of each connection to an AI service, identifies the signed app that opened it, and attributes it to the person behind the local account at the time. It doesn't decrypt TLS, install a root certificate, or read prompts, so claude, codex, and Cursor connect as they would without it, and none of the fixes on this page are needed for the agent's sake. You deploy it through your MDM, including Jamf, and endpoint provider policies set Allow or Block per provider for a group of devices. Each block lands in Arcjet Activity with the person, device, app, and provider.
For what an approved agent does after it connects, Arcjet enforces one policy in the hooks that Claude Code, GitHub Copilot, Cursor, OpenAI Codex, and Muse Code already fire. A hook sees each tool call and prompt inside the agent, before anything is encrypted, so an MCP allowlist or a sensitive information policy decides without decryption (Arcjet coding agents guide).
Arcjet has limits that matter here. It can't tell a personal account from a company account on the same hostname, so you still need the vendor tenant controls, and on Claude and ChatGPT those need inspection. It doesn't read prompts typed into a browser tab, it can't see devices without the agent, and a connection from an AI app can be an update check rather than a request. If you need prompt-level DLP across browsers and desktop apps, a decrypting product fits better: dope.security vs Arcjet compares an on-device gateway that inspects content with Arcjet's approach. For pricing and availability of the endpoint agent, book a demo.
Frequently asked questions
Why does Claude Code say SELF_SIGNED_CERT_IN_CHAIN?
A TLS-inspecting proxy, such as Zscaler or Netskope, sent its own root certificate in the chain, and Claude Code doesn't trust that root. The native installer, and npm installs on Node.js 22.15 or later, trust the operating system's store, so check that MDM installed the root there. Otherwise set NODE_EXTRA_CA_CERTS to the root's PEM file in the env block of ~/.claude/settings.json or managed settings, and restart Claude Code.
How do you fix unable to get local issuer certificate in Cursor?
Cursor recommends excluding its domains, including .cursor.sh, cursor.com, and *.cursor.com, from SSL inspection. If inspection must stay on, put the corporate root in the OS trust store, keep the http.systemCertificates setting on, and quit and reopen Cursor. Setting HTTP Compatibility Mode to HTTP/1.1 fixes blocked HTTP/2 streaming, not certificate trust.
Does Codex CLI use NODE_EXTRA_CA_CERTS?
Codex doesn't document it. Codex reads CODEX_CA_CERTIFICATE, a path to a PEM CA bundle, and falls back to SSL_CERT_FILE. The setting covers login, HTTPS requests, and WebSocket connections, so set it before codex login, and point it at a bundle that holds public roots plus your corporate root.
Should I put NODE_EXTRA_CA_CERTS in my shell profile or Claude Code settings?
Use the env block in ~/.claude/settings.json, or managed settings for a fleet. A shell export doesn't reliably reach Claude Code background agents, which run under a supervisor that inherits the environment of whichever shell started it first, and Claude Desktop sessions read the variable only from settings.
Is it safe to set NODE_TLS_REJECT_UNAUTHORIZED=0?
No. Node.js documents that 0 turns off certificate validation and makes HTTPS insecure. It applies to every connection the process and its subprocesses make, so an on-path attacker on an untrusted network can impersonate the API and read tokens and code. Trust the corporate root instead.
Why does curl work when pip or git fails?
Each tool uses its own trust store. A curl that trusts the OS keychain or store sees the corporate root, while Python Requests uses certifi and git uses the CA file its TLS library points to. Point REQUESTS_CA_BUNDLE or http.sslCAInfo at a combined bundle of public roots and the corporate root.
Can you detect shadow AI without SSL inspection?
Yes, for discovery. Resolver logs, OAuth grants, MDM inventory, and an endpoint agent that reads each connection's destination and signed app all work without decryption, and coding-agent hooks see tool calls inside the agent. None of them tells a personal Claude or ChatGPT account from a company one on the same hostname; the vendor tenant headers that do need inspection on those domains.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Arcjet hooks decide each coding agent tool call inside the agent, so they need no root certificate and no inspection bypass.