CERT_HAS_EXPIRED on Windows native install during OAuth login, no proxy/VPN/AV — curl succeeds on same host
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
claude /login fails with CERT_HAS_EXPIRED connecting to platform.claude.com during OAuth token exchange. Browser authorize step succeeds; CLI token exchange fails.
curl -vI https://platform.claude.com from same machine returns clean HTTP 200, valid cert. No proxy. VPN on/off tested, no change. No third-party AV (Defender only). System clock correct.
CLAUDE_CODE_CERT_STORE=system and BUN_USE_OPENSSL_CA=1 tried, no effect.
claude doctor shows "Search: OK (bundled)" succeeding but "Failed to fetch versions" failing in same run.
What Should Happen?
Login should complete, consistent with curl's successful TLS handshake to the same host.
Error Messages/Logs
Unable to connect to Anthropic services
Failed to connect to platform.claude.com: CERT_HAS_EXPIRED
SSL certificate error (CERT_HAS_EXPIRED). If you are behind a corporate proxy or TLS-intercepting firewall, set NODE_EXTRA_CA_CERTS to your CA bundle path, or ask IT to allowlist *.anthropic.com. Run /doctor for details.
Steps to Reproduce
- Install Claude Code on Windows via winget (Anthropic.ClaudeCode)
- Open PowerShell
- Run: claude auth /login
- Browser opens, click Authorize
- CLI fails to complete token exchange with: CERT_HAS_EXPIRED connecting to platform.claude.com
- Run: curl -vI https://platform.claude.com (in same shell) — returns clean HTTP 200, valid certificate
Claude Model
Not sure / Multiple models
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.193
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
Auto-update attempted to 2.1.195 per claude doctor.
Showing cached comments. Read the full discussion on GitHub ↗
13 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
The dup-bot is likely misfiling this — #71663 was a relative
NODE_EXTRA_CA_CERTSpath (a config error), and you've already ruled out the proxy / clock / AV causes that the canned error message assumes. The "curl succeeds, CLI fails withCERT_HAS_EXPIREDon the same host" pattern points at something more specific.The native CLI doesn't share curl's TLS stack. The native build runs on Bun, which validates TLS against its own bundled CA/trust store (BoringSSL) — separate from the OS/system store that
curl(and Defender) use. So a cleancurl -vItoplatform.claude.comtells you the system path is fine, but says nothing about the path Bun builds. That's whyBUN_USE_OPENSSL_CA=1andCLAUDE_CODE_CERT_STORE=system"having no effect" is itself a clue — the native build may not be honoring those, so it's still validating against the bundled store.CERT_HAS_EXPIREDspecifically (notUNABLE_TO_GET_ISSUER...) means a cert in the presented/built chain has aNotAfterin the past — and the classic "curl is fine but the stricter stack isn't" cause is an expired cross-signed root/intermediate that OpenSSL/curl routes around (it finds an alternate valid path) while the bundled store picks the expired link and stops. The fact thatclaude doctorshowsSearch: OK (bundled)butFailed to fetch versionsfailing in the same run fits: different hosts/endpoints, different chains, same runtime.To pin it down:
Look for any
notAfter=date in the past in the presented chain or the path your local store builds.Workarounds to try, in order:
claude install/ latest) — a newer Bun ships a newer bundled CA store, which is the cleanest fix if the bundled store is what's stale.BUN_USE_OPENSSL_CA=1didn't take, try giving it a current bundle explicitly: download Mozilla'scacert.pem(e.g. from the curl project) andset NODE_EXTRA_CA_CERTS=C:\full\path\to\cacert.pem— absolute path**, andexport/setit in the shell before launching (settings.jsonenvmay apply too late for the first TLS handshake; #71663 was exactly a relative-path version of this).openssl s_clientshows an expired cross-signed root in the chain, distrusting/removing that specific expired root from the Windows store lets the validator fall through to the still-valid path.If step 1 fixes it, the bundled CA store was stale; if step 2 fixes it, the native build isn't honoring the system store and needs an explicit bundle. Either way it's worth keeping this open separately from #71663 — the cause and fix are different.
---
For maintainers: the error text only suggests the corporate-proxy path, but this reporter has a clean
curl+ correct clock + no proxy, which the message doesn't cover. Surfacing which host and which cert (subject +notAfter) failed validation — and whether the native build actually honorsCLAUDE_CODE_CERT_STORE=system/BUN_USE_OPENSSL_CA=1— would separate "stale bundled store" from "expired cross-signed root in the chain" from "proxy MITM," which currently all collapse into oneCERT_HAS_EXPIREDline.Confirming this on a second machine with a clean (non-corporate-network) repro, plus a few additional data points:
Environment: Windows 11, native installer (not winget), no proxy/VPN/AV (Defender only), system clock correct.
Symptom: Same exact error, but not limited to OAuth token exchange — happens on every connection attempt, including just running claude with no subcommand:
Unable to connect to Anthropic services
Failed to connect to platform.claude.com: CERT_HAS_EXPIRED
Additional env vars ruled out (on top of CLAUDE_CODE_CERT_STORE=system and BUN_USE_OPENSSL_CA=1 from the original report):
NODE_USE_SYSTEM_CA=1 — no effect
NODE_EXTRA_CA_CERTS pointed at the correct intermediate chain file — no effect
That's now 4 different env vars across two reports that don't fix it.
Cert chain validates cleanly via openssl, not just curl:
openssl s_client -connect platform.claude.com:443 -showcerts
...
Verify return code: 0 (ok)
Full chain: platform.claude.com → Let's Encrypt YE1 → ISRG Root YE → ISRG Root X2 → ISRG Root X1. Cert was issued 2026-06-26, well within validity. Note the chain uses the newer ISRG Root YE/YE1 intermediates rather than the older X1/R3 chain — my guess is the bundled CA store in the native binary doesn't yet recognize this newer chain and is misreporting an unrecognized-issuer failure as CERT_HAS_EXPIRED.
Workaround that fixed it for me: downgrading from the latest channel to stable:
claude update --channel stable
This dropped me from 2.1.195 → 2.1.181, and the connection succeeded immediately afterward with zero other changes. Might help narrow down which release introduced the regression — possibly tied to the Bun runtime bump mentioned in #71727.
With a winget install, uninstalling and then installing version 2.1.181 (winget install --id Anthropic.ClaudeCode --version 2.1.181) worked for me as well. Wiped my settings, but I was able to login and use the terminal/cli interface again.
Fix for Windows native install:
CERT_HAS_EXPIREDduring OAuth loginIf
npm install -g @anthropic-ai/claude-code@2.1.177does not fix it andclaude -vstill shows 2.1.195, you have a native install shadowing the npm one.Run
where claude. If it listsC:\Users\<you>\.local\bin\claude.exe, that native binary is what actually runs, so npm downgrades never take effect.The real fix is to downgrade the native install:
claude install 2.1.177
Then verify with
claude -v(should read2.1.177). OAuth login works again.Root cause: versions ~2.1.181 through 2.1.195 bundle Bun 1.4.0, which has a TLS cert-verification regression that only breaks the OAuth login flow. 2.1.177 bundles Bun 1.3.14 and is unaffected. Auto-update can re-bump you, so disable it: set
DISABLE_AUTOUPDATER=1orautoUpdates: falsein settings.json.Confirming on the VS Code extension (anthropic.claude-code v2.1.195), not just the
native CLI — same CERT_HAS_EXPIRED on platform.claude.com during OAuth login. Windows,
no proxy/VPN/TLS-inspection, Defender only, clock correct. Reproduces by running the
extension's bundled binary directly (...\resources\native-binary\claude.exe), so it's
the shared Bun runtime, not the extension wrapper.
openssl s_client -showcerts confirms the served chain is fully valid (Gen Y, same as #71766):
CN=platform.claude.com notAfter Sep 24 2026
Let's Encrypt YE1 notAfter Sep 2 2028
ISRG Root YE notAfter Sep 2 2032
ISRG Root X2 notAfter Sep 2 2032
NODE_EXTRA_CA_CERTS with a roots-only Mozilla bundle does NOT resolve it; NODE_USE_SYSTEM_CA=1
is ignored. This is the Windows / CERT_HAS_EXPIRED variant of #71766.
It's happening to in 2.1.196
Basically can't use Claude.
set NODE_TLS_REJECT_UNAUTHORIZED=0 patched it for me
yes but that will be insecure
this fixed the issue:
claude install 2.1.177claude install 2.1.177
Thanks for this.
Environment
Symptom
Cowork and Code tabs in Claude Desktop fail consistently with:
API Error: Unable to connect to API: SSL certificate has expired
Requests retry ~8-10 times over 2-3 minutes before failing. The Chat tab is unaffected and works normally throughout.
Onset was sudden — began overnight (June 30 → July 1, 2026) with no known local config change at that time.
Troubleshooting already ruled out (in order)
Cert:\LocalMachine\Rootper common corporate-proxy advice. No effect.npm install -g @anthropic-ai/claude-code@2.1.177. Confirmed viaclaude -vandclaude doctor— clean diagnostics, all Remote Control/OAuth checks green.claude doctorthere. It resolved to the same patchednpm-global (2.1.177)binary with clean diagnostics — while, in the same window at the same time, the session's own request panel above showed "Request failed · retrying (8/10)"** and ultimately failed with the same SSL cert error.Key finding
Step 5 is the important one: the Cowork/Code session's request pipeline is a separate code path from the local CLI binary it's sitting next to. The correctly-patched, verified-working
claude.exe(2.1.177, Bun 1.3.14) is reachable and functional via manual invocation inside the very same Code tab terminal — but the session orchestration itself still fails. This suggests Desktop's Cowork/Code session engine uses its own internal runtime/cert bootstrap (possibly inside the sandboxed VM), independent of whatever CLI version is installed and resolvable on the host via PATH.This decouples the issue from anything CLI-version or Bun-version fixable on the user side — the standalone downgrade workaround does not apply to the Desktop session engine.
Question for maintainers
Does Claude Desktop's Cowork/Code session runtime bundle its own separate engine/cert handling independent of the host's installed Claude Code CLI (as reported by
claude -v)? If so, is there a Desktop-specific version or fix in progress, since CLI-side pinning doesn't touch it?Happy to provide
claude doctoroutput, screenshots, or further repro steps.Update for me for work-around that fixed it for me was removing an expired cert from my root stores then restarting claude desktop.
Root cause: the expired DST Root CA X3 cert (expired 2021, but still present in both Cert:\LocalMachine\Root and Cert:\CurrentUser\Root) was sitting alongside the valid ISRG Root X1. Whatever cert chain-building logic Desktop's Cowork/Code engine uses appears to have preferred building the path through the expired cross-sign rather than terminating at the valid self-signed root — a known, previously-documented Let's Encrypt chain-building pitfall.
Fix: removing that one expired cert from both root stores, then fully restarting Claude Desktop.
Powershell commands to tell if you have the same expired cert as me:
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.Subject -like "DST Root CA X3" } | Select-Object Subject, NotAfter, Thumbprint
Get-ChildItem -Path Cert:\CurrentUser\Root | Where-Object { $_.Subject -like "DST Root CA X3" } | Select-Object Subject, NotAfter, Thumbprint
(Mine was expired 9/30/2021)
Powershell command to remove it (need to run as administrator for machine store):
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.Subject -like "DST Root CA X3" } | Remove-Item
Get-ChildItem -Path Cert:\CurrentUser\Root | Where-Object { $_.Subject -like "DST Root CA X3" } | Remove-Item
But if system is a managed laptop, this might just be a temp fix as next policy sync could restore it.
<img width="844" height="466" alt="Image" src="https://github.com/user-attachments/assets/2d803763-88ff-49aa-a514-bfc6b6d443ba" />