エラーを読み解く
SSLエラーほど、Pythonプロジェクトを停滞させるものはありません。現在、ターミナルにこのようなメッセージが表示されているのではないでしょうか。
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1129)
これは紛らわしいエラーです。TLSやOpenSSLのバージョンが古いことを示唆していますが、それが原因であることは稀です。99%のケースでは、これはプロトコルの不一致が原因です。「暗号化」された通信を期待しているクライアントが、「プレーンテキスト」しか理解できないポートに対して通信を行おうとしています。
根本原因:言語の壁
このエラーは、クライアント(requestsを使用しているPythonスクリプトなど)が、標準的なHTTP用に構成されたサーバーポートに対して、セキュアなTLSハンドシェイクを開始したときに発生します。
コミュニケーションの行き違いとして考えてみましょう。
- クライアント: 「Client Hello」パケット(暗号化セッションを開始するためのバイナリ文字列)を送信します。
- サーバー: 暗号化の設定がされていません。バイナリデータを受け取っても認識できず、
HTTP/1.1 400 Bad Requestのようなプレーンテキストの文字列で応答します。 - 不一致: クライアントはその応答の最初の数バイトを確認します。通常はTLSバージョンのヘッダー(ハンドシェイクの場合は
0x16など)を期待しますが、代わりに「HTTP」の「H」(16進数で0x48)を受け取ります。OpenSSLは「H-T-T-P」をバージョン番号として解析しようとして失敗し、WRONG_VERSION_NUMBERエラーをスローします。
修正方法
1. URLとポートを監査する
まずは最も明白な原因である、接続文字列のタイポ(打ち間違い)から確認しましょう。ローカルの開発環境では特によくあります。FlaskやDjangoアプリをポート8000で実行している場合、通常、デフォルトではSSLは有効になっていません。
- 誤り:
https://127.0.0.1:8000 - 正しい例:
http://127.0.0.1:8000
本番環境のAPIで非標準ポート(8080や8443など)を使用している場合は、その特定のポートが実際にHTTPSをサポートしているか再確認してください。サイトにSSL証明書があるからといって、そのサーバーのすべてのポートが暗号化されているとは限りません。
2. NginxまたはApacheの設定を確認する
Nginxユーザーは、キーワードの欠落でミスをすることがよくあります。Nginxにポート443でリッスンするように指示しても、SSLを使用するように伝え忘れると、誰もがセキュアであることを期待するポートでプレーンなHTTPを提供してしまいます。
Nginxのサーバーブロックは以下のようになっている必要があります。
server {
listen 443 ssl; # ここで最も重要なのは 'ssl' キーワードです
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
location / {
proxy_pass http://localhost:3000;
}
}
このsslフラグがないと、Nginxは証明書を無視して生のテキストを送信し、クライアント側でエラーを発生させます。
3. Dockerのポートマッピングを修正する
Dockerでは、ポートが交差する可能性のある別のレイヤーが加わります。よくある間違いは、セキュアな外部ポートを、セキュアでない内部コンテナポートにマッピングしてしまうことです。例えば:
# https://localhost にアクセスしようとすると失敗します
docker run -p 443:80 my-web-app
このコマンドでは、ホストは443でリッスンしますが、トラフィックをコンテナ内のポート80に渡します。コンテナはおそらくポート80でHTTPを提供しているため、ハンドシェイクは即座に失敗します。プロトコルを一致させてください。ホストポートが443であれば、コンテナ化されたアプリが実際にSSL証明書で構成されていることを確認してください。
4. プロキシとロードバランサーのループを解決する
AWS ALB、Cloudflare、またはNginxリバースプロキシを使用している場合、「SSLオフロード(SSL Offloading)」が発生している可能性があります。これはプロキシがHTTPS接続を処理し、バックエンドとはHTTPで通信する仕組みです。バックエンドのコードが、ポート80でしかリッスンしていない別の内部サービスに対してHTTPS接続を強制しようとすると、このエラーが発生します。
修正の確認
推測に頼らず、curlを使用してポートを直接テストしてください。-v(verbose)フラグを使用すると、エラーが発生する前にサーバーが何を返しているかを正確に確認できます。
curl -vI https://your-api-endpoint.com
出力に * error:1408F10B:SSL routines:ssl3_get_record:wrong version number が含まれている場合、サーバーは間違いなくプレーンテキストを送信しています。また、OpenSSLを使用して生のレスポンスを確認することもできます。
openssl s_client -connect your-api-endpoint.com:443
CONNECTED(00000003) が表示された後にハングしたり、プレーンテキストのHTTPエラーが表示されたりする場合、問題はPythonコードではなくサーバーの設定にあります。
予防のヒント
設定エラーは、複雑なYAMLやJSONファイルの中に隠れていることがよくあります。この YAML ↔ JSON Converter のようなバリデーターを使用すると、Docker ComposeやKubernetesのマニフェストが本番環境にデプロイされる前に、構造的な問題をキャッチするのに役立ちます。
- 環境変数:
https://をハードコードしないでください。変数を使用して、ローカルテスト用のhttpと本番用のhttpsを切り替えられるようにします。 - 明示的に指定する: デバッグ時は、意図しないデフォルト値に当たらないよう、常にURLにポート(例:
:443)を含めてください。 - ヘッダーの監視:
X-Forwarded-Protoヘッダーを使用して、プロキシがHTTP経由で通信している場合でも、元のリクエストがセキュアであったかどうかをアプリケーションに知らせるようにします。

