二つの異なる障害が同じ pip のトレースバックを表示し、片方に合う修復がもう片方を悪化させます。トンネル越しのホストを名指しできない古いクライアントにはアップグレードが必要です。通信を再署名するプロキシには、ダウングレードではなく信頼されたルート証明書が必要です。経路が正反対に進むため、プロキシ変数を編集する前にどちらの障害かを判断してください。

設定に触れる前に最後のフレームを読む

トレースバックがどこで終わるかで、どの層が降参したかが分かります。終わりのフレームが pip に同梱された urllib3 の TLS コードの中にあり、テキストがホスト名について不平を言っているなら、クライアントライブラリは検証の手順を完了できておらず、それは証明書の問題よりもバージョンの問題であることがはるかに多いです。テキストが代わりに接続拒否、解決できないプロキシ名、または HTTP 407 を報告するなら、トンネルの終端点が原因であり、どんなアップグレードでもそれは変わりません。

失敗するシェルそのものの中で pip のバージョンとプロキシ変数を取得します。トンネルが上がる前に開いたシェルは、上がった後に開いたシェルとは異なる値を抱えており、その差分が多くの場合、診断の全体です。

python3 -m pip --version
env | grep -i -E 'proxy|no_proxy'

クライアントがトンネルより古い場合

古い pip のリリースには、ソケットがトンネルで包まれると宛先ホスト名を失う HTTPS スタックが同梱されていました。check_hostname のメッセージは、比較する名前がないまま検証に達したときにそのスタックが出すものです。アップグレードで不具合が消えるのは、新しいクライアントがハンドシェイクを通じて名前を保持するからであり、終わりのフレームが同梱の TLS コードを指すときはいつでもまず試す価値があるほど安価な変更です。

失敗するマシンからインデックスに到達できない場合は、到達できるホストで最新の wheel を取得し、そのファイルからインストールします。ブログ記事からコピーしたバージョンではなく、実際にダウンロードしたファイル名を使ってください。

python3 -m pip install --upgrade pip
python3 -m pip install ./pip-24.2-py3-none-any.whl

プロキシが通信を再署名する場合

TLS を終端し、企業ルートの下で証明書を再発行するプロキシは、ホスト名の不平ではなく certificate verify failed という別のエラーを生みます。クライアントはサーバーに到達したうえで示された身元を拒否したのですから、その違いが手がかりです。修復は、単一のコマンドでも PIP_CERT 変数でも、そのルートを明示的に信頼してクライアントがプロキシの提示するものへの連鎖を構築できるようにすることです。ルートをシステムの信頼ストアに追加する方法もありますが、それはマシン上のすべてのプログラムの信頼を変えることであり、一つのパッケージ導入を直すより大きな決断です。

--trusted-host は診断用に留め、常設の状態にはしないでください。残そうとしていた検査を切ってしまうので、本当に誤った証明書が後から健全に見えます。一つのパッケージを導入するために使ったなら、それを外し、次のリリースまでに連鎖を修復してください。

自分の Python コードは pip の設定をまったく無視します。requests の呼び出しでは、そのセッションから見える場所にプロキシとバンドルを渡す必要があり、下のブロックが両方をセッションオブジェクトに設定しているのはそのためです。

pip install --cert /usr/local/share/ca-certificates/corp-root.crt requests
export PIP_CERT=/usr/local/share/ca-certificates/corp-root.crt
s = requests.Session()
s.proxies = {"https": "http://10.20.30.40:8080"}
s.verify = "/usr/local/share/ca-certificates/corp-root.crt"
s.get("https://pypi.org/simple/", timeout=15)

終端点やポートが間違っている場合

トンネルクライアントは接続時にシステムプロキシを書き換えることが多く、そのため pip がそのポートで決して応答しないホストを指したままになることがあります。pip の設定を変える前に、pip ではないクライアントで終端点を試してください。同じプロキシ引数で curl が失敗するなら、不具合は pip より上にあり、pip の中のどんな設定も助けになりません。

コマンドのフラグと環境変数は、フラグが一度の呼び出しに、変数がその後起動するすべてのプロセスに適用されるという点で、異なる範囲に同じことを伝えます。内部インデックスには NO_PROXY を追加し、非公開のパッケージ通信がトンネル経由の回り道を避けるようにしてください。

curl -x http://proxy.corp.internal:8080 --head https://pypi.org/simple/
pip install --proxy http://proxy.corp.internal:8080 requests

設定を永続化し、経路が機能することを証明する

現在のシェルより長く残す必要がある場合は、プロキシを pip 自身の設定に保存し、その後 pip が実際に何を使うかを読み返してください。debug の出力は各値の背後のファイルと変数を示すので、優先順位についての議論はドキュメントを読むより速く決着します。

検証は意図的に二つのクライアントを使います。標準ライブラリの要求は経路と信頼の連鎖を証明し、ダウンロードは pip 自身の設定がそれらと一致することを証明します。最初が成功して二番目が失敗するとき、違いはネットワークではなく pip の内部にあります。

pip config set global.proxy http://proxy.corp.internal:8080
pip config debug
python3 -c "import urllib.request as r; print(r.urlopen('https://pypi.org/simple/').status)"
pip download --no-deps -d /tmp/wheelcheck requests
What you seeFirst commandRepair
hostname message inside vendored TLS framespython3 -m pip --versionupgrade the client
certificate verify failed after the tunnelcurl -x http://proxy.corp.internal:8080 --head https://pypi.org/simple/trust the corporate root
connection refused or HTTP 407env | grep -i -E 'proxy|no_proxy'correct host, port or credentials

関連記事