Contents

【WSL2ユーザー向け】 Cloudflare WARP × WSL2 で起きる証明書エラーの罠に対処した話 【トラブルシューティング】

受託開発の案件で取引先からWindows PCを貸与してもらい、WSL2を入れて開発環境を構築していたところ、一部のツールのインストールスクリプトが証明書エラーで動かないという問題に遭遇しました。 原因を調べていくと、Cloudflare WARP + WSL2という組み合わせでしか起きない問題だったことが分かりました。ハマる人は限られそうですが、そのぶん情報も少なかったので、作業ログを残しておきます。

背景

  • 貸与PCには、Cloudflare WARPが常時接続になるようなポリシーが入っていました
    • 定期的に接続状態をチェックして、切れていれば強制的に接続し直す仕組みです。セキュリティ上は妥当な仕組みなので、これを外すのはナシです
  • 開発はWSL2上で行う予定で、AIエージェントツール(Claude Codeなど)の利用も取引先から許可をもらっています
  • ちなみに、職場の他のメンバーは全員MacBookを貸与されていました(ここ、後で効いてきます)

症状: uvのインストールスクリプトが動かない

  • WSL2を入れて、必要なツールを順番にインストールしていたところ、例えばuvのインストールスクリプトがエラーで止まってしまいました

    $ curl -LsSf https://astral.sh/uv/install.sh | sh
    curl: (60) SSL certificate problem: (略)
    
  • 試しにcurl単体でアクセスしてみても、同じように証明書エラーになります

    $ curl https://ollama.com
    curl: (60) SSL certificate problem: (略)
    More details here: https://curl.se/docs/sslcerts.html
    
    curl failed to verify the legitimacy of the server and therefore could not
    establish a secure connection to it. (以下略)
    
    • 最近のツールは curl ... | sh 形式のインストールスクリプトで配られていることが多く、スクリプトの中でもcurlを呼んでいるので、ダウンロード先によっては途中まで進んでからコケたりします
    • 後で分かったことですが、Ollamaのクラウドモデルも通らない側で、Ollama経由のClaude Codeも使えませんでした
  • 周りに聞いてみても、全員MacBookなので、誰もこんな問題には遭遇していません。自分だけ……

    • どうやら同じWARPでも、Macではこの問題は起きないようです
  • 切り分けてみると、以下のことが分かりました

    • WARPを切ると、普通に通る
    • WARP接続中でも、Windows側のブラウザからは普通に見られる
    • WARP接続中のWSL2でも、全部がダメというわけではなく、例えば cloudflare.com や api.anthropic.com には普通につながる。通るサイトと通らないサイトがある
  • というわけで、WindowsのWARP + WSL2という組み合わせで、しかも一部のサイトだけで起きる問題、ということになりました

原因: WARPのTLSインスペクション

  • WARPのON/OFFで、実際に返ってくる証明書を比べてみます

    $ openssl s_client -connect ollama.com:443 -servername ollama.com -showcerts </dev/null 2>/dev/null \
        | openssl x509 -noout -issuer -subject
    
    • WARP OFFのときは本来の証明書(Let’s Encryptなど)が返ってきますが、WARP ONのときは発行者が Gateway CA - Cloudflare managed G1 になっていました
    • つまり、WARP(Cloudflare Gateway)がTLSインスペクション(HTTPS通信を一度復号して中身を検査し、Cloudflareの証明書で暗号化し直して転送する仕組み)を行っている、ということです
    • 逆に、通るサイトはWSL2が証明書を信頼できている、つまり本来の証明書のまま届いているということなので、TLSインスペクションの対象外になっていると考えられます。検査対象になっているサイトだけが通らない、というわけです(Cloudflare Gatewayでは検査しないサイトを設定で指定できるので、そのあたりの設定の違いだと思われます)
  • Windows側のブラウザで普通に見られるのは、Windowsの証明書ストアにこのCA証明書が入っていて、信頼されているからです

  • 一方、WSL2は独立したLinux環境なので、Windowsの証明書ストアは見ていません

    • WSL2のcurlから見ると「知らないCAが署名した証明書」なので、検証に失敗するというわけです
    • WARPはWindows側の証明書ストアには証明書を入れてくれますが、WSL2の中までは面倒を見てくれない、ということですね

対処: 使われている証明書を特定してWSL2に登録する

やることは「WSL2にもこのCA証明書を信頼させる」だけなのですが、どの証明書を入れればいいのかを突き止めるところで一番時間がかかりました。

1. 配布されている証明書では通らない

  • ネットで「Cloudflare WARP WSL2」あたりを調べると、Cloudflareのドキュメントで配布されている証明書(Cloudflare_CA.pem)をダウンロードして入れる、という手順がよく出てきます
    • AIに聞いたときも、そう答えられたことがありました
  • しかし、今回実際に使われていたのは Gateway CA - Cloudflare managed G1 という証明書でした
    • どちらもCloudflareの証明書ではあるのですが、配布されている Cloudflare_CA.pem の中身を見てみると Cloudflare for Teams ECC Certificate Authority という別の証明書で、しかも2025年2月に有効期限が切れていました

      $ openssl x509 -in Cloudflare_CA.pem -noout -subject -enddate
      subject=C=US, ST=California, L=San Francisco, O=Cloudflare, Inc, CN=Cloudflare for Teams ECC Certificate Authority
      notAfter=Feb  2 16:05:00 2025 GMT
      
    • 古い記事の手順どおりにこれを入れても、今回のケースでは通らなかったはずです

  • 確実なのは、Windows側で実際に使われている証明書を取り出すことです

2. Windows側で証明書を探してエクスポートする

  • PowerShellで、Cloudflare関連のルート証明書を探します

    PS> Get-ChildItem Cert:\LocalMachine\Root |
          Where-Object { $_.Subject -match "Cloudflare" -or $_.Issuer -match "Cloudflare" } |
          Format-List Subject, Issuer, Thumbprint
    
    • Cert:\CurrentUser\Root 側に入っている場合もあります
    • 組織によってはCloudflareという名前の付かない独自のCA証明書を使っていることもあるので、上の openssl で確認した発行者名で探すのが確実です
  • 見つかったら、certmgr.msc の「信頼されたルート証明機関」から該当の証明書を右クリック →「すべてのタスク」→「エクスポート」で、Base64エンコード X.509(.cer) 形式で保存します

3. WSL2の証明書ストアに登録する

  • エクスポートしたファイルをWSL2側にコピーして登録します

    $ sudo cp /mnt/c/Users/orenoaccount/Downloads/gateway-ca.cer /usr/local/share/ca-certificates/gateway-ca.crt
    $ sudo update-ca-certificates
    Updating certificates in /etc/ssl/certs...
    1 added, 0 removed; done.
    
    • Ubuntu/Debian系の update-ca-certificates は、拡張子が .crt のファイルしか拾わないので、コピーするときに拡張子を変えておきます
  • これで、WARP接続中でも curl -v https://ollama.com が通るようになりました。インストールスクリプトも普通に動くようになります

4. 常駐しているサービスは再起動する

  • ただし、証明書を登録する前から動いていたサービスは、再起動するまで古いままです

    • 自分の場合は、Ollama(Go製で、公式インストーラーで入れるとsystemdのサービスとして常駐します)がこれに当たり、curlは通るのにOllamaのクラウドモデルだけ証明書エラーのまま、という状態になりました
    • Goは、システムのCA証明書をプロセスの中で1回だけ読み込んで使い回すため、起動済みのプロセスは新しい証明書を知りません
  • 自分はWindows側から wsl --shutdown でWSL2ごと再起動して解決しましたが、該当のサービスだけ再起動すれば足りるはずです

    $ sudo systemctl restart ollama
    

補足: 環境変数は不要でした

  • 「Node.jsやPythonは独自にCA証明書を持っているので、環境変数で指定が必要」という話もよく見かけるので、NODE_EXTRA_CA_CERTS や REQUESTS_CA_BUNDLE も設定してみましたが、今回は不要でした(外しても問題なく動いています)
    • もちろん、使うツールによっては必要になる場合もあるので、うまくいかないときの切り分けの候補として覚えておくとよいと思います

運用メモ

  • WARPの証明書は、組織によっては定期的に更新(ローテーション)されることがあるようです
    • しばらくしてまた証明書エラーが出始めたら、まず openssl s_client で証明書が変わっていないかを確認します
    • 入れ直したら、update-ca-certificates と常駐サービスの再起動をセットで行います
  • この証明書は、取引先のWARP(Cloudflare Gateway)の通信検査を信頼するためのものなので、WSL2の環境を私物のPCなどに移すときには持っていかないように注意します
  • 根本的には、開発に使うドメインをTLSインスペクションの対象外にしてもらうよう、情報システム部門に相談するのが理想です(なかなかそうもいかないのが現実ですが……)

まとめ

  • WindowsのWARP + WSL2の組み合わせでは、TLSインスペクションの証明書がWSL2に入らないため、検査対象になっているサイトだけcurlやインストールスクリプトが証明書エラーになる
  • 入れる証明書は、ネットで配られているものではなく、Windows側で実際に使われている証明書を特定してエクスポートするのが確実(配布版の Cloudflare_CA.pem は別物で期限切れだった)
  • 登録は /usr/local/share/ca-certificates/ に .crt で置いて update-ca-certificates
  • 証明書を登録する前から動いている常駐サービス(Ollamaなど)は、再起動を忘れずに
この記事は qiita.com にも掲載しています。