PythonのrequestsやurllibでHTTPS通信をすると、次のようなSSLエラーで止まることがあります。「python ssl」で検索して来た人が実際に遭遇するもっとも多いのがこのメッセージです。
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)requests経由だと、同じ原因がこの形で出ることもあります。
requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)')))このエラーはサーバーが危険なわけではなく、Python側がサーバー証明書を検証する材料(ルート証明書)を持っていない・古い・見つけられないときに出るのがほとんどです。この記事では、まずエラー文から原因を切り分け、検証を無効化せずに正しく直す方法を原因別に解説します。
まずエラー文で原因を切り分ける
SSLエラーはメッセージの末尾を見ると原因がほぼ特定できます。よく出るものを対応表にまとめます。
| エラー文(末尾) | 主な原因 | 対応セクション |
|---|---|---|
| unable to get local issuer certificate | ルート証明書が無い/古い(最頻・特にMac) | 原因1 |
| self signed certificate in certificate chain | 社内プロキシ・自己署名証明書 | 原因2 |
| certificate has expired | 証明書の期限切れ(サーバー側) | 原因4 |
| hostname '...' doesn't match | ホスト名の不一致 | 原因4 |
| tlsv1 alert protocol version / no protocols available | 古いTLS/OpenSSL | 原因5 |
原因1: ローカルにルート証明書が無い(最頻・特にMac)
unable to get local issuer certificateはこのパターンです。Pythonは証明書の検証にcertifiというルートCAの束を使いますが、これが無い・古いと検証に失敗します。まずはcertifiを更新します。
pip install --upgrade certifiMacでpython.org版のPythonを使っている場合
macOSでpython.orgのインストーラからPythonを入れた場合、システムの証明書とリンクされておらずこの状態になりがちです。Python同梱の証明書インストールスクリプトを一度だけ実行すると解決します(3.xは自分のバージョンに置き換えてください)。
/Applications/Python\ 3.x/Install\ Certificates.commandこれは内部でcertifiをインストールし、sslのデフォルト証明書パスに紐付けます。Homebrew版やpyenv版のPythonでは通常この問題は起きません。
原因2: 社内プロキシ・自己署名証明書(企業ネットワーク)
self signed certificate in certificate chainが出る場合、社内プロキシ(Zscaler等)やファイアウォールがHTTPS通信を検査するために独自のルート証明書を挿入していることが多いです。この場合はその社内ルート証明書をPythonに信頼させるのが正攻法です(検証は無効化しない)。
方法A: verifyでCAバンドルを指定する
import requests
response = requests.get(
'https://internal.example.com',
verify='/path/to/corp-root-ca.pem', # 社内ルート証明書(PEM)
)方法B: 環境変数で指定する(コードを変えずに済む)
requestsはREQUESTS_CA_BUNDLE、標準sslはSSL_CERT_FILEを参照します。両方設定しておくと確実です。
export REQUESTS_CA_BUNDLE=/path/to/corp-root-ca.pem
export SSL_CERT_FILE=/path/to/corp-root-ca.pem社内CAが複数ある場合は、certifiのバンドル末尾に社内証明書を追記する方法もあります。python -m certifiでバンドルのパスを確認できます。
原因3: 中間証明書の欠落(サーバー側のチェーン不備)
ブラウザでは開けるのにPythonだけ失敗する場合、サーバーが中間証明書を配信していないことがあります。ブラウザは中間証明書を補完してくれますが、Pythonは補完しないためチェーンが切れて検証に失敗します。まずopensslでチェーンを確認します。
openssl s_client -connect example.com:443 -showcerts出力に中間CAが含まれていなければサーバー側の設定不備です。サーバーを直せない場合は、中間証明書を含む完全なチェーンのPEMを用意し、原因2と同じくverify=で指定して回避します。
原因4: 証明書の期限切れ・ホスト名の不一致
certificate has expiredはサーバー証明書が失効しています。基本はサーバー側の更新待ちで、自分のコード側では直せません(一時的に通したい場合のみ後述の応急処置)。
hostname '...' doesn't matchはアクセス先URLのホスト名が証明書のCN/SANと一致しないケースです。URLのスペルミス、IP直打ち、内部向けホスト名などが原因になりやすいので、まずURLを確認します。
import requests
try:
requests.get('https://expired.badssl.com/')
except requests.exceptions.SSLError as e:
print(f'SSL Error: {e}') # メッセージ末尾で原因を確認原因5: 古いTLSバージョン・OpenSSL
tlsv1 alert protocol versionやno protocols availableは、サーバーが古いTLS(1.0/1.1)しか話せない、または手元のOpenSSLが古いことが原因です。バージョンを確認します。
python -c "import ssl; print(ssl.OPENSSL_VERSION)"
python --versionPython 3.10以降はデフォルトでTLS 1.2未満を拒否します。相手が更新できないレガシーサーバーの場合は、後述のカスタムSSLContextで許可TLSを下げる回避が可能ですが、セキュリティ上は非推奨です。
応急処置: 検証を一時的に無効化する(本番禁止)
開発中に今すぐ通したいときの最終手段がverify=Falseです。中間者攻撃を防げなくなるため本番では絶対に使わないでください。
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
response = requests.get('https://example.com', verify=False)無効化の詳しい方法(urllib3・ssl各ライブラリでの書き方)と安全な代替は、PythonでSSL証明書エラーが出るときの最終兵器にまとめています。
より細かく制御する: カスタムSSLContext
CAの指定だけでなくTLSバージョンや暗号スイートまで制御したい場合はSSLContextを使います。ただしrequests.get()にssl_context引数は無いため、HTTPAdapter経由で渡すのが正しい方法です(この書き方を間違えている情報が多いので注意)。
import ssl
import requests
from requests.adapters import HTTPAdapter
from urllib3.poolmanager import PoolManager
class SSLContextAdapter(HTTPAdapter):
def __init__(self, ssl_context=None, **kwargs):
self.ssl_context = ssl_context
super().__init__(**kwargs)
def init_poolmanager(self, connections, maxsize, block=False, **kwargs):
self.poolmanager = PoolManager(
num_pools=connections, maxsize=maxsize, block=block,
ssl_context=self.ssl_context,
)
ctx = ssl.create_default_context(cafile='custom_ca.pem')
# 例: 必要ならTLS下限を調整(レガシー対応・非推奨)
# ctx.minimum_version = ssl.TLSVersion.TLSv1_2
session = requests.Session()
session.mount('https://', SSLContextAdapter(ssl_context=ctx))
response = session.get('https://example.com')多くのケースでは原因1〜3のverify=指定で足ります。ここまでの制御が必要になるのはクライアント証明書認証やTLS要件が特殊な場合だけです。
まとめ
PythonのSSLエラーは、原因さえ切り分けられれば検証を無効化せずに直せます。
- まずエラー文の末尾を見て原因を特定する(冒頭の対応表)
unable to get local issuer certificate:certifi更新/MacはInstall Certificates.commandself signed certificate in certificate chain: 社内ルートCAをverify=やREQUESTS_CA_BUNDLEで信頼させる- 中間証明書欠落は
openssl s_clientで確認し、完全なチェーンを指定する verify=Falseは開発時の最終手段のみ。本番では使わない