背景Pythonプロジェクトを停滞させる要因として、突然のSSL handshake failureほど厄介なものはありません。StripeやTwilioのようなREST APIを呼び出している場合、ローカルのLinuxマシンではすべて問題なく動作していても、macOSや制限のある企業内ネットワークに移行した途端にスクリプトがクラッシュすることがあります。これは、接続先のサーバーの身元を検証するための有効なroot certificatesのセットをPythonが見つけられないために発生します。
これをセキュリティの検問所と考えてください。スクリプトはサーバーのIDカード(SSL certificate)を確認しますが、それを発行した機関を認識できません。信頼できる機関のローカルリストがない限り、Pythonは潜在的なセキュリティ漏洩を防ぐために接続を拒否します。
環境とエラーメッセージこのエラーは、macOS VenturaやSonoma上のPython 3.6以降で最も一般的です。また、ZscalerやCisco Umbrellaのような、内部証明書でトラフィックを再署名してSSL inspectionを行う企業用ファイアウォールの背後で作業しているWindowsやLinuxのデベロッパーも、この問題に悩まされることがあります。
正確なエラーは通常、以下のようになります:
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1123)>
根本原因のデバッグStackOverflowにあるあらゆる修正方法を試す前に、Pythonが具体的にどこでtrust storeを探しているかを確認してください。ターミナルまたはIDEで以下のスニペットを実行します:
import ssl
import os
print("Default Verify Paths:", ssl.get_default_verify_paths())
print("SSL_CERT_FILE:", os.environ.get('SSL_CERT_FILE'))
print("SSL_CERT_DIR:", os.environ.get('SSL_CERT_DIR'))
もし openssl_cafile が None であるか、存在しないパスを指している場合、それが問題の原因です。Pythonは実質的に「目隠し」状態で動作しています。
解決策### 1. macOS特有の修正python.orgの公式インストーラー経由でPythonをインストールした場合、macOSのシステムキーチェーンは使用されません。代わりに、証明書が一つも含まれていない独自の内部OpenSSLバージョンを使用します。インストールに同梱されているコマンドスクリプトを実行することで、5秒で修正できます:
# 自身のバージョンに合わせて3.11を変更してください
/Applications/Python\ 3.11/Install\ Certificates.command
このスクリプトは certifi パッケージをインストールし、Pythonがバンドルの場所を認識できるようにシンボリックリンクを作成します。
2. Certifiの更新と環境変数ローカルの証明書バンドルが古すぎて、新しいCertificate Authorities (CAs)を認識できない場合があります。certifi ライブラリは、24時間365日更新される厳選されたRoot Certificatesのコレクションを提供します。まず、最新バージョンをプルすることから始めましょう:
pip install --upgrade certifi
次に、シェルプロファイル(.bashrc または .zshrc)で環境変数を設定し、Pythonにこの特定のバンドルを強制的に使用させます:
export SSL_CERT_FILE=$(python -m certifi)
export REQUESTS_CA_BUNDLE=$(python -m certifi)
3. プロキシ環境への対応(エンタープライズ向け)企業環境では、脅威をスキャンするために会社がSSLトラフィックを傍受している可能性があります。certifi は会社のプライベートなRoot CAを知らないため、ここでは機能しません。会社の証明書を手動でtrust storeに追加する必要があります。まず、企業の証明書を .pem ファイルとしてエクスポートし、既存のバンドルの末尾に追加します:
# 現在のcertifiバンドルのパスを確認
python -m certifi
# 出力例: /usr/local/lib/python3.11/site-packages/certifi/cacert.pem
# 企業の証明書をファイルの末尾に追加
cat company_root.pem >> /path/to/your/certifi/cacert.pem
4. 「一時的」な(安全ではない)回避策自己署名証明書を使用してローカルサーバーをテストしており、セキュリティが優先事項ではない場合は、検証をバイパスできます。このコードを本番環境(production environment)で絶対に使用しないでください。 中間者攻撃(MITM)に対して無防備になります。
import requests
# requestsライブラリの場合
response = requests.get('https://localhost:8000', verify=False)
# グローバルなurllibコンテキストの場合
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
検証:修正されたか?Pythonが主要なサイトと安全なハンドシェイクを確立できるようになったか、以下のクイックチェックを実行してください:
import requests
try:
response = requests.get('https://google.com', timeout=5)
print(f"Success! Status code: {response.status_code}")
except Exception as e:
print(f"Connection failed: {e}")

