安全与治理 · 收录于 2026-08-30 · 适用 2 工具 ✓ 实测验证

JDK 老项目升级 TLS 握手失败:Received fatal alert: protocol_version / No appropriate protocol

已验证 2026-08-30JDK 老项目升级 TLS 握手失败——客户端 java.security 里 jdk.tls.disabledAlgorithms 显式禁 SSLv3/TLSv1/TLSv1.1,强制走 TLS1.2+。

快速修复

# 客户端全局禁用旧协议(改 java.security,最常用)
# $JAVA_HOME/jre/lib/security/java.security(Oracle)或 $JAVA_HOME/conf/security/java.security(OpenJDK)
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, MD5withRSA, DH keySize < 768

# 只影响当前应用(JVM 启动参数;-Dhttps.protocols 只管 HttpsURLConnection,
# -Djdk.tls.client.protocols 管全局客户端)
-Dhttps.protocols=TLSv1.2,TLSv1.3 -Djdk.tls.client.protocols=TLSv1.2,TLSv1.3

# 服务端侧(nginx)
ssl_protocols TLSv1.2 TLSv1.3;

你会遇到什么

老项目(JDK 1.6/1.7/1.8 早期)升级依赖、迁移服务器或开启 HTTPS 后,HTTPS 调用突然失败:

  • 客户端抛 javax.net.ssl.SSLHandshakeException: Received fatal alert: protocol_version 或 No appropriate protocol (protocol is disabled or cipher suites are inappropriate);
  • 服务端 openssl s_client -connect <host>:<port> -tls1 能通,但 -tls1_2 连不上(服务端只支持旧协议);或反过来,新服务端拒绝老客户端。
本文路径:快速修复(30 秒一段命令)→ 一步步排查(可跳读)→ 原理与变体(症状/原因/证据)。赶时间直接跳到「快速修复」。

为什么会这样

TLS 握手要求客户端与服务端的「可用协议集合」交集非空;交集为空时服务端发 alert protocol_version(alert 号 70),Java 侧就是 SSLHandshakeException。三种典型坑:

坑机制
服务端只开 TLSv1.0/1.1新 JDK/OpenSSL 默认已禁旧协议,客户端没有可协商协议
老 JDK 默认最高 TLSv1.0JDK 1.7 及以下默认不启用 TLS1.2,连不上只支持 TLS1.2+ 的新服务端
代码/配置硬编码旧协议setEnabledProtocols("TLSv1")、SSLv3、自定义 SSLContext、跳过主机名验证

JDK 各版本默认能力(原文详表摘要):

JDK 版本默认 TLS 能力关键点
1.6最高 TLSv1.0上 TLS1.2 需换 JDK
1.7.0_79支持 TLS1.2 但默认未启用手动启用 + 禁 TLSv1/1.1
8u112TLS1.2;不支持 TLS1.3TLSv1/1.1 默认未禁用,需手动禁
8u261~8u340TLS1.3 服务端默认启用8u341+ 客户端也默认启用
11.0.0~11.0.10TLS1.2/1.3;TLSv1/1.1 未禁11.0.11+ 默认禁 TLSv1/1.1
JDK 11 早期 TLS1.3 有会话恢复/死循环 bug(JDK-8211806 不发 SNI、JDK-8212885 证书链丢失、JDK-8213202 竞态、JDK-8214418 100% CPU),11.0.8 前不建议直接用 TLS1.3。

TLS 握手是「协议集合交集」协商:客户端把自己支持的协议列表发给服务端,服务端选交集;交集为空就发 alert protocol_version(70),Java 侧表现为 SSLHandshakeException。

问题永远出在「某一侧的能力集合」与时代脱节:服务端只开 TLSv1.0/1.1(新客户端默认已禁)或客户端太老(JDK ≤1.7 默认最高 TLSv1.0,连不上只支持 1.2+ 的服务端),或代码硬编码旧协议。

JDK 版本默认能力随时间演进(8u112 支持 1.2 不支持 1.3;8u341+ 客户端默认启用 1.3;11.0.11+ 默认禁 1.0/1.1)。

确定性推论:握手失败先确定两侧能力——服务端 openssl s_client -tls1_2 -connect <host>:<port> 测协议;客户端 -Djavax.net.debug=ssl:handshake 看发送的协议列表;修法按侧:服务端开新协议/客户端升级 JDK/代码去掉硬编码协议;

老 JDK 上 1.3 有已知 bug(JDK-8211806 等),11.0.8 前不建议直接用;

判断路径:alert 70 = 协议交集为空,不是证书/密码套件问题。

一步步来

1. 先判断哪边有问题 — 必做

# 对目标 HTTPS 端口三连测:1.2 通 / 1.0、1.1 不通 → 服务端只支持新协议,问题在客户端
openssl s_client -connect <host>:<port> -tls1_2
# 1.0 通 / 1.2 不通 → 服务端还是旧协议,先升级服务端
openssl s_client -connect <host>:<port> -tls1

2. 客户端:禁用旧协议(三种粒度) — 必做

  • 全局(推荐):java.security 的 jdk.tls.disabledAlgorithms 加 TLSv1, TLSv1.1,重启应用;影响该 JDK 上所有应用,若有老应用依赖 TLSv1/1.1 会连失败,先确认。
  • JVM 参数(当前应用):-Dhttps.protocols=TLSv1.2,TLSv1.3 -Djdk.tls.client.protocols=TLSv1.2,TLSv1.3。
  • 代码层(单连接):connection.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"});Spring Boot 用 server.ssl.enabled-protocols: TLSv1.2, TLSv1.3。

3. 排查硬编码与跳过校验 — 排查

grep -rn "setHostnameVerifier" --include="*.java" .
grep -rn "TLSv1\|SSLv3" --include="*.java" .

重点:HttpsURLConnection.setEnabledProtocols、Apache HttpClient 的 SSLConnectionSocketFactory 协议列表、RestTemplate 自定义 SSL 上下文、生产环境严禁跳过证书验证的 HostnameVerifier。

4. 验证 — 必做

openssl s_client -connect <host>:<port> -tls1_2   # 应该成功
openssl s_client -connect <host>:<port> -tls1     # 应该失败
openssl s_client -connect <host>:<port> -tls1_1   # 应该失败

应用日志无 SSLHandshakeException / NoSuchAlgorithmException,打印的协议版本为 TLSv1.2。

怎么确认修好了

三连测结果符合「1.2 通、1.0/1.1 拒」,且应用日志握手版本为 TLSv1.2+;Java 侧不再抛 Received fatal alert: protocol_version。

其它环境的表现

环境说明
服务端(nginx/openssl)ssl_protocols TLSv1.2 TLSv1.3;,等价于实测中的「新服务端」场景
OpenSSL 3.5默认安全级已禁 TLSv1.0/1.1,与 JDK 8u261+/11.0.11+ 默认禁旧协议同机制
未实测JDK 1.6/1.7/8 各版本默认行为表来自原文,本机无老 JDK 未复现

症状

HTTPS 调用报 SSLHandshakeException: Received fatal alert: protocol_version 或 No appropriate protocol;openssl 三连测中 TLS1.0/1.1 与 TLS1.2 结果不对称(一边通一边不通)。

原因

客户端与服务端可用 TLS 版本集合交集为空:服务端仅旧协议(TLSv1.0/1.1)、客户端默认禁旧协议;或老 JDK 默认最高 TLS1.0 连新服务端;或代码硬编码旧协议。

解法

  1. openssl 三连测定位哪边受限(-tls1 / -tls1_1 / -tls1_2);
  2. 服务端:nginx ssl_protocols TLSv1.2 TLSv1.3;,升级老 openssl;
  3. 客户端:java.security jdk.tls.disabledAlgorithms 加 TLSv1, TLSv1.1(全局),或 -Djdk.tls.client.protocols=TLSv1.2,TLSv1.3(当前应用),或代码 setEnabledProtocols(单连接);
  4. 排查并清理 TLSv1/SSLv3 硬编码与跳过校验的 HostnameVerifier;
  5. 三连测验证:1.2 通、1.0/1.1 拒。
验证证据
  • 机制实测(2026-08-30,WSL Ubuntu,OpenSSL 3.5.5):本地 TLS1.0-only 服务端 → TLS1.2 客户端握手失败(unsupported protocol);TLS1.0 客户端成功。反向 TLS1.2-only 服务端 → TLS1.0 客户端报 tlsv1 alert protocol version(即 Java 侧 Received fatal alert: protocol_version 的底层),TLS1.2 客户端成功。证据:实测复现日志(2026-08-30)。
  • 未实测:本机无 JDK 1.6/1.7/8,版本默认行为表来自原文《万字长文!TLS协议升级避坑手册》(净重21克,2026-08-26),未在本机复现;OpenSSL 3.5 默认安全级已禁 TLS1.0/1.1,与新版 JDK 默认禁旧协议机制相同。

相关 canonical

按「前置 → 同根 / 同域 / 相关」读:

适用工具 (2)

本问题方案已挂到下列工具的方案页——从工具页也能回到这里,双向可达。

jdk · 外部工具openssl · 外部工具

来源与证据

收录 验证 ✓ 实测验证适用 2 工具
方案含本地实测或来源可复核的解法;未实测项已在文中如实标注。

素材来源:https://www.cnblogs.com/xsge/p/22703862

← 安全与治理 问题域 | 问题货架 | 遇到其它问题?去 问题空间 提交求助卡