Claude CodeのmacOS版が社内ゲートウェイの証明書を拒否するときの対処
NodeやPythonは通るのにmacOSのネイティブ版Claude Codeだけが社内のHTTPSゲートウェイを拒否するとき、NODE_EXTRA_CA_CERTSで直す手順と切り分け方を示します。
macOSのネイティブ版Claude Codeが、社内ゲートウェイ(証明書がTelia Root CA v2のチェーンで署名されたHTTPSゲートウェイ)にUNABLE_TO_GET_ISSUER_CERTで繋がらないことがあります。同じマシンのNode.jsやPythonは同じゲートウェイに通るのに、claudeだけが落ちるのが特徴です。2.1.292の報告(#100203)では、CAバンドルのファイルをNODE_EXTRA_CA_CERTSで渡すと、TLS検証を無効にせずに通りました。
この記事は、その症状に絞って切り分けと対処を書きます。プロキシ越しのSSLエラー一般はClaude CodeのSSL証明書エラーの原因と対処法が受け持つので、ここでは「OSの証明書ストアにルートがあるのに拒否される」場合だけを扱います。
症状の見分け方 — 他のランタイムが通るかを見る
見るべきは、同じエンドポイントを別のランタイムで叩いたときの結果です。報告されている症状は次のとおりです。
| 確認対象 | 結果 |
|---|---|
ネイティブ版claude(NODE_EXTRA_CA_CERTSなし) | 結果終了コード1。UNABLE_TO_GET_ISSUER_CERT(unable to get issuer certificate) |
ネイティブ版claude(CAバンドルを指定) | 結果終了コード0。応答が返る |
Node.jsのhttps.get() | 結果環境変数なしで検証に成功し、HTTP 200 |
Pythonのssl.create_default_context() | 結果Homebrewのバンドルを使って検証に成功 |
環境は、macOS 26.7.1(Apple Silicon)、Claude Code 2.1.292のMach-Oネイティブ実行ファイル、ゲートウェイはLiteLLM 1.103.1で、認証はapiKeyHelperが返すベアラートークンです。ゲートウェイは葉証明書、中間CA(Telia Server CA v3)、自己署名のルート(Telia Root CA v2)の3枚を送っています。つまり、チェーンは欠けていません。
ここが重要な分かれ目です。サーバーが完全なチェーンを送っていて他のランタイムが通るなら、ゲートウェイ側の設定ミスよりも、クライアントの信頼元の側を疑います。報告者はルート証明書がmacOSのSystemRootCertificates.keychainにもHomebrewのバンドルにも入っていることをフィンガープリントで確かめています。
切り分けは3コマンドで済む
手元で同じ分岐を再現するには、claudeを呼ぶ前に別の経路でチェーンを見ます。gateway.example.internalは自社のホスト名に置き換えてください。
# 1. ゲートウェイが送るチェーンの枚数と発行元を見る
openssl s_client -connect gateway.example.internal:443 -showcerts </dev/null
# 2. Nodeが環境変数なしで検証できるか見る
node -e "require('https').get('https://gateway.example.internal/', r => console.log(r.statusCode))"
# 3. claudeを debug ログ付きで1回だけ呼ぶ
claude -p "Reply with exactly: ok" --debug-file ~/claude-tls.log2で数字が出て3だけが失敗するなら、この記事の症状に当たります。2も失敗するなら、Nodeでも検証できていないので、ゲートウェイのチェーンやルートの配布そのものを情報システム部門に確かめます。--debug-fileの使い方はClaude Codeの--debugと--debug-fileでログ出力先を切り替えるにあります。
失敗時のログには、次のように信頼元の読み込み結果が残ります(報告に載っている出力です)。
CA certs: stores=bundled,system, extraCertsPath=undefined
CA certs: Loaded 121 bundled root certificates
CA certs: Dropped 2 expired certificate(s) from system store
CA certs: Loaded 18 system CA certificates
Connection error details: code=UNABLE_TO_GET_ISSUER_CERT (SSL error), message=unable to get issuer certificate読むべき行はLoaded ... system CA certificatesです。この環境ではシステムストアから18枚が読まれています。一方、同じ報告でNode.js v22.23.1は145のルートを持ち、Homebrewのバンドルは203枚です。数の差だけで原因は断定できません(報告者自身もそう書いています)が、信頼元の手がかりとしてログの数字は記録しておく価値があります。
対処 — 手元にあるバンドルをNODE_EXTRA_CA_CERTSで渡す
回避策は、既存のCAバンドルのパスを環境変数に入れてから起動することです。報告ではHomebrewのOpenSSLが持つバンドルを使っています。
export NODE_EXTRA_CA_CERTS=/opt/homebrew/etc/openssl@3/cert.pem
claudeこれで、TLS検証を保ったまま、分離した再現と対話セッションの両方が直ったと報告されています。公式のネットワーク設定ドキュメントも、社内CAを信頼させる方法としてNODE_EXTRA_CA_CERTS=/path/to/ca-cert.pemを挙げています。
パスは環境で違います。Homebrewを使っていないマシンでは、次のような選択肢があります。
- 情報システム部門が配布する社内CAのPEMファイルを使う
- ルートと中間CAを1ファイルにまとめた自前のバンドルを作る
- macOSのキーチェーンから証明書をPEMで書き出す(
security find-certificate -a -p <キーチェーン>の出力をファイルに保存する形。macOS標準のsecurityコマンドで書き出せます)
どれを使っても、ファイルにゲートウェイのルート証明書が含まれていることが条件です。報告の環境では、ルートがOS側にあってもバンドルを渡すまで拒否されていました。
バンドルにルートが入っているかを先に確かめる
パスを渡す前に、そのファイルに目的のルートが入っているかを調べておくと、ログを読む前に空振りを避けられます。報告では、ルートの識別にSHA-256フィンガープリントを使っています。openssl x509 -noout -fingerprint -sha256は、PEM1枚ずつにしか使えないため、バンドルを分割して調べるか、ルートのPEMだけを別ファイルに取り出して照合します。
# ルート証明書1枚のフィンガープリントを出す
openssl x509 -in root-ca.pem -noout -fingerprint -sha256
# バンドル内の証明書の枚数を数える
grep -c "BEGIN CERTIFICATE" /opt/homebrew/etc/openssl@3/cert.pem報告のバンドルは203枚で、問題のルートとTeliaSonera Root CA v1の両方を含んでいます。自前でまとめる場合は、ルートと中間CAのPEMをcatで連結して1ファイルにすれば足ります。
毎回exportしたくないとき
~/.claude/settings.jsonのenvブロックに書くと、シェルごとにexportしなくて済みます。パスは自分の環境のものに置き換えます。バックグラウンドセッションにも届かせたい場合の考え方はSSL証明書エラーの記事の該当節にあります。
{
"env": {
"NODE_EXTRA_CA_CERTS": "/opt/homebrew/etc/openssl@3/cert.pem"
}
}シェルの~/.zshrcに書く場合は、すでに起動しているセッションには反映されないため、書いた後にClaude Codeを起動し直します。
効いたかをログで確かめる
NODE_EXTRA_CA_CERTSは、パスが間違っていても起動時にエラーになりません。公式ドキュメントも、/statusの「Additional CA cert(s)」はパスを表示するだけで、ファイルが読めたかは確かめていないと説明しています。デバッグログで次の行を探します。
CA certs: stores=bundled,system, extraCertsPath=/opt/homebrew/etc/openssl@3/cert.pem
CA certs: Appended extra certificates from NODE_EXTRA_CA_CERTS (/opt/homebrew/etc/openssl@3/cert.pem)Appended extra certificatesの行があれば、ファイルは読めています。読めなかったときは、理由付きのFailed to readまたはFailed to loadの行が代わりに出ます。報告の成功ケースでも、読み込まれる数は失敗時と同じ(バンドル121枚、システム18枚)で、その後ろに追加分が足されています。
原因はどこまで分かっているか
この症状の原因は、#100203の段階では特定されていません。報告者は、失敗の原因を「ネイティブクライアントの信頼元か、証明書パスの扱い」までしか絞れていません。候補として、同梱ルートの不足、システムストアの列挙、クロス署名された証明書の選択、そのほかのパス構築の挙動が残っています。
手がかりになりそうな事実が2つあります。
System.keychainには、同じ件名のTelia Root CA v2をTeliaSonera Root CA v1が発行した「クロス署名版」も入っています。経路の選び方に影響するかは未確認です。- 関連する報告があります。macOSのネイティブ版がシステム証明書を少数しか読まずに主要なAPIリクエストが失敗した件(#72712。報告では10枚しか読まれず、
CLAUDE_CODE_CERT_STOREを変えても変わらないとされ、issue自体は閉じています。この報告では、有効なNODE_EXTRA_CA_CERTSを読み込ませても失敗が残っていました)、古いバージョンでの同様のTLS失敗(#74588)、Linuxでクロス署名ルートが絡む経路構築の失敗(#71766)です。
つまり、「ネイティブ版がOSのストアを十分に読めていない」型の報告は繰り返し出ています。ただし、今回のTeliaの件が同じ原因だとは確かめられていません。追加バンドルで通るかどうかも、環境によって分かれる可能性があります。
効かない回避と、検証を無効にする回避
効かない可能性が高いのは、CLAUDE_CODE_CERT_STOREの組み合わせを変える方法です。公式の既定値はbundled,systemで、systemだけにする指定もありますが、#72712では値を変えても読まれる枚数が変わらなかったと報告されています。今回の症状も、既定のままでシステムストアから読まれたうえで失敗しています。
やってはいけないのはNODE_TLS_REJECT_UNAUTHORIZED=0のような検証の無効化です。今回の報告も、TLS検証は一度も無効にしていません。#100203では追加バンドルで通っており、検証を切らずに済む手段が先にあります。バンドルを足しても失敗が残った場合も、検証を切るのではなく、情報システム部門にチェーンとルートの配布を確かめます。
よくあるつまずき
NODE_EXTRA_CA_CERTSを設定したのにまだ失敗する: パスの打ち間違いを疑い、上のログ行で読み込めたかを確認します。ファイルが空でないか、PEM形式かも見ます- プロキシも使っている環境: 報告の環境では社内HTTPSプロキシが設定され、ゲートウェイのホスト名は
NO_PROXYに入っていました。プロキシを通る宛先と通らない宛先でCAの出どころが違うことがあるので、HTTPS_PROXYとNO_PROXYの値も併せて控えておくと原因を絞りやすくなります。プロキシの設定そのものはClaude Codeプロキシ設定にまとめています apiKeyHelperでトークンを返している: 認証の失敗とTLSの失敗は見え方が別です。UNABLE_TO_GET_ISSUER_CERTはTLSの段階で出るので、トークンの値を疑う前に証明書を先に見ます- インストール方法を変えれば直るか: npm版はNodeが信頼元を読むため、挙動がネイティブ版と違う可能性はありますが、この症状で試した結果は報告にありません。ネイティブとHomebrewの違いはClaude Code Macインストールで比べています
報告するときに添える情報
社内の情報システム部門やissueに状況を伝えるときは、次の項目がそろうと再現しやすくなります。報告#100203が載せていた項目に沿っています。
- Claude Codeのバージョンと、ネイティブ実行ファイルかnpm版か
- OSのバージョンとCPU(今回はmacOS 26.7.1、Apple Silicon)
- 比較に使ったNode.jsのバージョン
- ゲートウェイの種類(今回はLiteLLM)と認証方法(
apiKeyHelperなど) - ゲートウェイが送るチェーンの構成と、ルートのフィンガープリント
CLAUDE_CODE_CERT_STOREとNODE_EXTRA_CA_CERTSが未設定の状態でのデバッグログ- 指定ありでのデバッグログ(成功した場合)
ホスト名や資格情報は伏せても、この比較は成り立ちます。報告者も内部のホスト名・資格情報・アカウント情報を省いたうえで再現手順を書いています。
まとめ
NodeやPythonが通るのにmacOSのネイティブ版だけが社内ゲートウェイを拒否するなら、チェーン不備ではなくクライアントの信頼元を疑います。ルートを含むCAバンドルをNODE_EXTRA_CA_CERTSに指定し、デバッグログのAppended extra certificatesで読み込みを確かめれば、#100203の環境では検証を保ったまま通りました。ただし#72712では追加バンドルを読み込んでも失敗が残っており、必ず効くわけではありません。ログのLoaded ... system CA certificatesの枚数は、報告するときの有力な材料です。