エラーメッセージ
javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
別の表記
- Remote host terminated the handshake
JDK 21 と 25 は「Remote host terminated the handshake」と出力します。Java 8 のログでは、同じ状況が「SSL peer shut down incorrectly」を原因とする「Remote host closed connection during handshake」として表示されることがよくあります。
Java (JSSE) — the TCP connection closed during the handshake without a TLS alert
このエラーの意味
JVM が TLS ハンドシェイクの途中にあるときに、相手側(またはその間にある何か)が TLS アラートを送信せずに TCP 接続を閉じました。Java には理由を知る手段がなく、接続が終了したことしかわかりません。典型的には、ファイアウォール、プロキシ、ロードバランサーがハンドシェイクを遮断したか、サーバーが ClientHello を何も通知せずに拒否しています。
発生する理由
経路上のプロキシやファイアウォール
よくあるHTTPS プロキシが必須のネットワーク、SNI や JA3/JA4 フィンガープリントでブロックするディープパケットインスペクションのファイアウォール、VPN は、ブロックした接続を何も通知せずに終了させることがよくあります。
サーバーが拒否したハンドシェイクを切断している
よくある一部のサーバーやロードバランサーは、プロトコルバージョン、暗号スイートのリスト、SNI のいずれかが受け入れられない場合に、アラートで応答せずに接続を閉じます。
SNI がない
ときどきIP アドレスで接続したり、jsse.enableSNIExtension=false を指定したりすると、サーバー名が送信されません。CDN のエッジは、こうしたハンドシェイクを遮断することがよくあります。
接続数の上限とレート制限
ときどき接続数の上限に達したサーバーや、バーストトラフィックを受けたレートリミッターは、新しい接続をリセットします。この場合、エラーは断続的に発生します。
修正方法
- 1.
ハンドシェイクをトレースし、openssl と比較する
javax.net.debug で、ハンドシェイクがどこまで進んだかがわかります。同じホストからの openssl s_client でも切断される場合、原因は JVM ではなく、ネットワークかサーバーです。
bash java -Djavax.net.debug=ssl:handshake -jar app.jar 2>&1 | grep -E 'ClientHello|server_name|EOF|terminated' # Same handshake without Java: is it the network or the JVM? openssl s_client -connect api.example.com:443 -servername api.example.com -brief </dev/null - 2.
ネットワークのプロキシを使う
Java はデフォルトでは OS やブラウザーのプロキシ設定を使いません。https.proxyHost/https.proxyPort(または -Djava.net.useSystemProxies=true)を設定し、内部のホストは http.nonProxyHosts で除外してください。
bash # Corporate networks: route HTTPS through the proxy the browser uses java -Dhttps.proxyHost=proxy.example.net -Dhttps.proxyPort=3128 \ -Dhttp.nonProxyHosts="localhost|*.internal.example.com" -jar app.jar - 3.
SNI を送信する
URL にはホスト名を使い、SNI 拡張は有効なままにしてください。
text Connect with the host name (not an IP address) so JSSE sends SNI, and do not set -Djsse.enableSNIExtension=false: front ends that need SNI close such connections.