[BUG] OAuth login/refresh fails with UNABLE_TO_GET_ISSUER_CERT on platform.claude.com's new Let's Encrypt (ISRG Root X2 cross-signed) chain

Open 💬 2 comments Opened Jun 27, 2026 by Norghul

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?

On a headless Linux server, claude login fails with:

OAuth error: SSL certificate error (UNABLE_TO_GET_ISSUER_CERT). 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.

The error message is misleading: there is no proxy and no TLS interception. The actual cause is that Claude Code's bundled runtime cannot build a valid TLS chain to platform.claude.com after Anthropic rotated that host onto Let's Encrypt's new 2026 hierarchy (leaf → Let's Encrypt YE1ISRG Root YEISRG Root X2 cross-signed by ISRG Root X1) on 2026-06-26.

The chain is validopenssl, the system Node runtime, browsers, and fetch() all verify it (Verify return code: 0 (ok)). Only Claude Code's bundled runtime fails. api.anthropic.com uses a Google Trust Services / GlobalSign chain and is unaffected, so normal API traffic works — the failure is isolated to OAuth login and token refresh, which hit platform.claude.com.

Impact

Any user performing a fresh OAuth login or token refresh after the Jun 26 rotation, whose environment relies on Claude Code's bundled root store (minimal/headless/container images, some distros), will be unable to authenticate. API key auth (ANTHROPIC_API_KEY) is unaffected.

Environment

  • Claude Code: 2.1.193 — also reproduced on 2.1.181 (not version-specific), installed via pnpm
  • OS: Fedora (Linux 7.0.10)
  • System Node: nvm v24.18.0 (used only for diagnostics; Claude runs its own bundled runtime)
  • No proxy, no MITM, system clock correct

Root cause: two hosts, one runtime, opposite outcomes

| Host | Served chain | Claude runtime |
|------|--------------|----------------|
| api.anthropic.com | GTS WE1GTS Root R4GlobalSign Root CA (long-established) | OK — reachable (returns 401, i.e. TLS OK) |
| platform.claude.com | LE YE1ISRG Root YEISRG Root X2 (cross-signed by ISRG Root X1) — issued Jun 26 2026 | FAIL — UNABLE_TO_GET_ISSUER_CERT |

Same runtime, same machine — the only variable is the certificate chain. The new cross-signed ECDSA path is the trigger.

platform.claude.com chain (openssl s_client -showcerts):

0 s:CN=platform.claude.com              NotBefore: Jun 26 2026   (reissued)
  i:Let's Encrypt, CN=YE1
1 s:Let's Encrypt, CN=YE1
  i:ISRG, CN=Root YE
2 s:ISRG, CN=Root YE                    NotBefore: May 13 2026   (new)
  i:ISRG, CN=ISRG Root X2
3 s:ISRG, CN=ISRG Root X2               NotBefore: May 13 2026   (new, cross-signed)
  i:ISRG, CN=ISRG Root X1
Verify return code: 0 (ok)

Evidence it is a client-side path-building bug (not Anthropic, not the OS)

All of the following succeed against platform.claude.com / console.anthropic.com using the same machine's trust material:

# openssl, system store
echo | openssl s_client -connect platform.claude.com:443 -servername platform.claude.com 2>/dev/null \
  | grep 'Verify return code'
# => Verify return code: 0 (ok)

# system Node https
node -e 'require("https").get("https://api.anthropic.com",r=>console.log("OK",r.statusCode))'
# => OK 404

# system Node global fetch / undici
node -e 'fetch("https://console.anthropic.com/v1/oauth/token",{method:"POST"}).then(r=>console.log("OK",r.status))'
# => OK 415

# explicit ca = system bundle, all hosts
node -e 'const fs=require("fs"),https=require("https");const ca=fs.readFileSync("/etc/pki/tls/cert.pem");
for(const h of ["api.anthropic.com","console.anthropic.com","claude.ai"])
  https.get({host:h,path:"/",ca},r=>console.log(h,r.statusCode)).on("error",e=>console.log(h,e.code));'
# => all OK (301/403/404)

Relevant Claude Code debug log lines (--debug):

[DEBUG] CA certs: stores=bundled,system, extraCertsPath=<system bundle>
[DEBUG] CA certs: Loaded 120 bundled root certificates
[DEBUG] CA certs: Loaded 813 system CA certificates
[DEBUG] CA certs: Appended extra certificates from NODE_EXTRA_CA_CERTS
[DEBUG] mTLS: Creating HTTPS agent with custom certificates
...
[ERROR] OAuth refresh failed (expected): unable to get issuer certificate
[ERROR] Failed to fetch oauth profile from OAuth token: ... 401
[ERROR] [Bootstrap] fetchBootstrapData failed: AxiosError: Request failed with status code 401

Note the runtime does load bundled + system + extra certs, yet still cannot complete the platform.claude.com path — strongly suggesting the custom HTTPS agent's path-builder does not use the server-supplied intermediates and/or cannot bridge the new ISRG Root X2 cross-sign to ISRG Root X1.

Workaround

Hand the runtime the full served chain plus the ISRG roots:

echo | openssl s_client -connect platform.claude.com:443 -servername platform.claude.com -showcerts 2>/dev/null \
  | awk '/-----BEGIN CERTIFICATE-----/{f=1} f; /-----END CERTIFICATE-----/{f=0}' > ~/lechain.pem
curl -fsSL https://letsencrypt.org/certs/isrgrootx1.pem   -o ~/x1.pem
curl -fsSL https://letsencrypt.org/certs/isrg-root-x2.pem -o ~/x2.pem
cat ~/lechain.pem ~/x1.pem ~/x2.pem /etc/pki/tls/cert.pem > ~/claude-ca.pem
export NODE_EXTRA_CA_CERTS=~/claude-ca.pem
claude   # logs in successfully

To persist (note: ~ does not expand inside double quotes, so use $HOME):

echo 'export NODE_EXTRA_CA_CERTS="$HOME/claude-ca.pem"' >> ~/.bashrc
/etc/pki/tls/cert.pem is the Fedora/RHEL system bundle path — on Debian/Ubuntu it's /etc/ssl/certs/ca-certificates.crt. Adjust if your distro differs.

Pointing NODE_EXTRA_CA_CERTS at just the system root bundle is not sufficient — the new YE1/ISRG Root YE/X2-cross intermediates must be present locally, because the runtime does not appear to use the server-supplied ones.

What Should Happen?

OAuth login/refresh validates platform.claude.com exactly as openssl, browsers, and stock Node do, using the server-supplied intermediates and the trusted ISRG Root X1 anchor.

Error Messages/Logs

Steps to Reproduce

  1. On a Linux host whose root store relies on Claude Code's bundled set (or any environment exhibiting the failure), run claude and attempt OAuth login (or let an existing token refresh) after 2026-06-26.
  2. Observe OAuth error: SSL certificate error (UNABLE_TO_GET_ISSUER_CERT).
  3. Confirm with --debug: the OAuth refresh to platform.claude.com fails while api.anthropic.com calls only 401.

Claude Model

_No response_

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.195

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

Other

Additional Information

_No response_

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗