Error [only supports OpenSSL 1.1.1+]

ImportError: urllib3 v2.0 only supports OpenSSL 1.1.1+ の直し方(古い OpenSSL 環境)

FIX SUMMARY verified
Applies when
python:3.7-slim-stretchurllib3 2.x (import-time OpenSSL check)importing urllib3 2.x (or requests) where the linked OpenSSL is a real OpenSSL older than 1.1.1 (Amazon Linux 2 / old Debian-Ubuntu / CentOS-RHEL 7); macOS LibreSSL only warns (NotOpenSSLWarning) and imports fine

Verified: reproduced in python:3.7-slim-stretch, then the only supports OpenSSL 1.1.1+ signature was gone after the fix (exit 0).

import requestsimport urllib3(あるいはそれらに依存するライブラリの読み込み)で、次のエラーが出て止まることがあります。

ImportError: urllib3 v2.0 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'OpenSSL 1.1.0l  10 Sep 2019'. See: https://github.com/urllib3/urllib3/issues/2168

'OpenSSL ...' の部分は環境ごとに変わり、OpenSSL 1.0.2k のように別の版番号になることもあります。先頭の v2.0 も、urllib3 2.0.x では v2.0、2.1 以降では v2 と表示が変わる可変部分です。これは、入っている urllib3 が 2 系で、その Python がリンクしている OpenSSL が 1.1.1 より古いために起きます。

なぜ import で止まるのか

urllib3 は 2.0(2023 年)で、OpenSSL 1.1.1 未満のサポートを打ち切りました。1.1.1 で入った API に依存する実装へ切り替えたためです。urllib3 2 系は読み込まれた瞬間(urllib3/__init__.py)に、Python の ssl モジュールがどの OpenSSL でコンパイルされたかを調べ、1.1.1 より古ければその場で ImportError を投げます。requests は内部で urllib3 を使うので、import requests でも同じ経路をたどります。

止まっているのはインストールではなくimport です。pip install は成功していて、実行時に初めて版の不一致が表面化します。

なぜ古い OpenSSL の環境で出るのか

手元の開発機は OpenSSL が新しい(1.1.1 以降、あるいは 3.x)ので、urllib3 2 系がそのまま読み込めます。エラーになるのは、Python がリンクしている OpenSSL が 1.1.1 より古い環境です。次のような場所で出ます。

  • Amazon Linux 2(OpenSSL 1.0.2):EC2 の AL2 で踏みやすい定番です。AWS Lambda では Python 3.8〜3.11 ランタイム(Amazon Linux 2・OpenSSL 1.0.2)で踏み、3.12 以降(Amazon Linux 2023・OpenSSL 3.x)では踏みません。
  • 古い Debian / Ubuntu:Debian 9(stretch)は 1.1.0、Ubuntu 16.04 は 1.0.2 で、いずれも境界を下回ります。
  • CentOS / RHEL 7(OpenSSL 1.0.2)。

同じ Python の版でも、リンクされている OpenSSL の版で出たり出なかったりします。手元(新しい OpenSSL)では通るのに、古い OS のコンテナや CI、Lambda で落ちる、という形になります。

直し方1:urllib3 を 1 系に固定する(OpenSSL を上げられないとき)

OpenSSL を上げられない環境(レガシー OS、AL2 の Lambda など)では、urllib3 を 2 未満に固定するのが第一手です。1.26 系は古い OpenSSL でも動きます。

pip install "urllib3<2"

requirements.txt や制約ファイルに書くなら次のようにします。

urllib3<2

requests を使っていて requests 自身は据え置きたい場合も、依存する urllib3 側を固定すれば読み込めるようになります。すでに 2 系が入っているなら、pip install "urllib3<2" で入れ直せば 1.26 系へ下がります。

これは版を固定するだけなので、OpenSSL には触れません。urllib3 1.26 系は新機能の追加は終了していますが、セキュリティ修正は継続しています。古い環境をそのまま使うあいだの措置と考え、OpenSSL 1.1.1+ に移行できたらこのピンは外します。

直し方2:OpenSSL 1.1.1+ の環境へ上げる(恒久)

urllib3 2 系を使い続けるなら、OpenSSL が 1.1.1 以降の環境に移すのが本筋です。urllib3 の版でなく土台を直すので、他のライブラリの将来の要求にも耐えます。

  • コンテナ / OS を新しくする:Debian なら bullseye・bookworm(OpenSSL 1.1.1/3.x)、Amazon Linux なら AL2023 に上げます。ベースイメージのタグを新しいものへ替えるだけで OpenSSL も上がります。
  • macOS:Xcode Command Line Tools 付属の Python(/usr/bin/python3)は LibreSSL にリンクされていますが、urllib3 2 系の import 自体は成功します(この ImportError にはならず、別問題です)。どうしても OpenSSL 版が必要なら、python.org 版や Homebrew の Python を使います。これらは新しい OpenSSL にリンクされています。
  • どの OpenSSL でビルドされているかを確認する:次で今の版が分かります。
python -c "import ssl; print(ssl.OPENSSL_VERSION)"

これが 1.1.1 以降(または OpenSSL 3.x)になっていれば、urllib3 2 系はそのまま読み込めます。1.1.01.0.2 など実 OpenSSL の 1.1.1 未満と出るなら、直し方1で固定するか、環境を上げます。LibreSSL … と出る場合はこの ImportError ではなく別問題なので、後述の切り分けを参照してください。

切り分け(うまくいかないとき)

  • import requests で出た:requests が依存する urllib3 が原因です。requests を下げる前に、python -c "import ssl; print(ssl.OPENSSL_VERSION)" で OpenSSL の版を確かめ、urllib3 側を固定するか環境を上げます。
  • urllib3<2 を入れたのに、まだ 2 系が読み込まれる:別の依存が urllib3 2 系を引き戻していないかを pip show urllib3 で確認します。制約ファイル(pip install -c constraints.txt -r requirements.txt、中身に urllib3<2)で上限を揃えられます。ただし別の依存が urllib3>=2 を要求している場合は両立せず解決エラーになるので、その依存を対応版へ更新するか OpenSSL を上げます。
  • ssl.OPENSSL_VERSIONLibreSSL … の場合:urllib3 2 系は import 時に別の警告(NotOpenSSLWarning)を出しますが、import 自体は成功し、この ImportError にはなりません。macOS の /usr/bin/python3(Xcode Command Line Tools・LibreSSL)が典型で、TLS 機能に不安があれば python.org や Homebrew の OpenSSL 版 Python を使います。これは本記事の ImportError とは別問題です。
  • エラーが出ずに SSL: CERTIFICATE_VERIFY_FAILED などの別の SSL エラーになる:それは版の不一致ではなく証明書側の問題で、原因も対処も別です。まず今回の import が通るかを先に確認してから切り分けます。

検証環境

  • python:3.7-slim-stretch(OpenSSL 1.1.0l)、ネットワーク有り
  • 再現:pip install "urllib3>=2" の後に import urllib3 すると、urllib3 v2.0 only supports OpenSSL 1.1.1+ を出して終了コード 1
  • 修正:pip install "urllib3<2" の後に import urllib3 すると、終了コード 0

再現から修正までは errfix の検証ハーネスが機械的に確認しています。境界が OpenSSL の版であって Python の版でないことは、対照として python:3.7-slim-buster(OpenSSL 1.1.1n・同じ Python 3.7)で urllib3 2 系が読み込めることを確かめて裏づけました。エラー文言は実機出力から引用しています。Amazon Linux 2(OpenSSL 1.0.2)や古い Debian/Ubuntu・CentOS/RHEL 7 といった実 OpenSSL 1.1.1 未満の環境については、機械検証の対象は上記の 2 点に限り、一般的な引き金として urllib3 の issue #2168 に沿って書いています。なお macOS の LibreSSL はこの ImportError の引き金ではなく(NotOpenSSLWarning になり import は成功します)、別問題です。

検証(machine-verified)

この修正は python:3.7-slim-stretch のバージョン固定コンテナ内で再現し、修正後に only supports OpenSSL 1.1.1+ のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs python/urllib3-openssl-1-1-1
● reproduce only supports OpenSSL 1.1.1+ present ✓
● apply fix exit 0
● re-run only supports OpenSSL 1.1.1+ gone ✓
PASS verified · python:3.7-slim-stretch · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。