错误信息
javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
也可能输出为
- Remote host terminated the handshake
JDK 21 和 25 打印的是“Remote host terminated the handshake”。在 Java 8 的日志中,同样的情况通常显示为“Remote host closed connection during handshake”,其原因(cause)为“SSL peer shut down incorrectly”。
Java (JSSE) — the TCP connection closed during the handshake without a TLS alert
含义
在 JVM 进行 TLS 握手的过程中,对端(或中间的某个设备)关闭了 TCP 连接,且没有发送 TLS 告警。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 默认不使用操作系统或浏览器的代理设置;设置 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.