# No appropriate protocol（Java）原因和修法

> Java 报 No appropriate protocol，是因为启用的 TLS 版本全被禁用或没有可用的加密套件，多半是代码写死了 TLSv1。改用 TLS 1.2 及以上即可。

Source: https://ssl.sh/zh/errors/java-no-appropriate-protocol/
Last updated: 2026-11-10

**`javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)` 的意思是：Java 手里已经没有可以使用的 TLS 版本了。** 你的代码启用的每一个协议，要么被 JDK 安全策略禁用，要么没有可用的加密套件。这个异常在本地发生，Java 甚至还没联系服务器；最常见的原因是代码或配置写死了 TLS 1.0 或 1.1。

## 这条报错是什么意思

握手开始时，JSSE 会算出一个“可用协议”列表：在启用的协议（来自 `SSLContext`、`setEnabledProtocols`、`jdk.tls.client.protocols` 或某个库的配置）里，去掉被 `jdk.tls.disabledAlgorithms` 禁用的，再去掉没有任何可用加密套件的。列表为空，`HandshakeContext` 就抛出这条异常，ClientHello 不会发出去。

从 JDK 8u291（2021 年 4 月 20 日）、11.0.11 和 16 开始，`jdk.tls.disabledAlgorithms` 里包含了 `TLSv1` 和 `TLSv1.1`。只请求这两个版本的代码，现在得到的就是一个空列表。新版 JDK 禁用的东西更多：Oracle 加密路线图写明，JDK 24 及以后禁用了 TLS_RSA 加密套件，只指定这类套件的代码也会以同样的方式失败。

有一条相近的报错要区分：客户端启用了 TLS 1.2/1.3，但服务器只能用 TLS 1.0，握手会走得更远，然后报 `The server selected protocol version TLS10 is not accepted by client preferences [TLS13, TLS12]`。报错不同，要修的一方也不同。

这个报错常在升级 JDK 之后出现，因为禁用 TLS 1.0/1.1 是通过补丁版本下发的，不需要换大版本。很多团队只是把 JDK 8 升到新的小版本，或者把基础镜像换成新标签，代码一行没改，就开始报这个错。排查时先对比出问题前后的 `java -version` 输出，基本就能确认。

## 常见原因（按出现频率排序）

多数情况下是代码或依赖库写死了 TLSv1 或 TLSv1.1。

### 1. 代码或依赖库写死了 TLSv1 / TLSv1.1

典型写法：`SSLContext.getInstance("TLSv1")`、`socket.setEnabledProtocols(new String[] {"TLSv1"})`、HTTP 客户端构造时传入协议数组、`-Dhttps.protocols=TLSv1`、邮件属性 `mail.smtp.ssl.protocols=TLSv1`，或者老版本 JDBC 驱动的连接参数限制了 TLS 版本。

- **确认：** 搜索代码和配置：

```bash
grep -rnE "TLSv1([^.]|\.1)|SSLv3|https\.protocols|ssl\.protocols|enabledTLSProtocols|tlsVersions" src/ config/ *.properties *.yml 2>/dev/null
```

再用 `-Djavax.net.debug=ssl:handshake` 跑一次，找 `No available cipher suite for ...` 和被忽略的协议。

- **修复：** 删掉写死的协议，改用 `SSLContext.getInstance("TLS")`，让 JDK 启用它支持的最新版本（当前版本为 TLS 1.3 和 1.2）。升级写死 TLS 1.0 的老驱动和老依赖。

### 2. Java 服务器只配置了老协议

Tomcat、Jetty 或 Spring Boot 里配了 `server.ssl.enabled-protocols=TLSv1` 之类的设置，JDK 一升级，所有客户端连进来都会在服务器端失败。

- **修复：** Spring Boot 用 `server.ssl.enabled-protocols=TLSv1.3,TLSv1.2`；Tomcat 在 `SSLHostConfig` 的 `protocols` 属性里只保留 `TLSv1.2` 和 `TLSv1.3`。

### 3. 指定的加密套件被禁用或不匹配

“cipher suites are inappropriate”指的是代码把启用的加密套件限定成了一个全都用不了的列表：JDK 24+ 上只有 `TLS_RSA_*`、只有 3DES 或 RC4，或者只有 TLS 1.3 套件却没启用 TLS 1.3。可以在[加密套件查询](https://ssl.sh/zh/tools/cipher-suite-lookup/)里查每个套件。

- **修复：** 不要在应用代码里指定加密套件，或者改成现代的 ECDHE AEAD 套件。

### 4. 加固过的或自定义的 java.security

公司的基础镜像、FIPS 配置或 `-Djava.security.properties` 文件，可能禁用了比默认更多的东西。

- **确认：**

```bash
grep -n -A4 "^jdk.tls.disabledAlgorithms" "$JAVA_HOME/conf/security/java.security"          # JDK 9+
grep -n -A4 "^jdk.tls.disabledAlgorithms" "$JAVA_HOME/jre/lib/security/java.security"      # JDK 8
ps -ef | grep -o "java.security.properties=[^ ]*"
```

## 分平台修复命令

各平台的修法都是让 JVM 启用 TLS 1.2 和 TLS 1.3：去掉写死的旧协议，或者用 JVM 参数指定新版本。

**任意系统，客户端 JVM 参数：**

```bash
java -Djdk.tls.client.protocols=TLSv1.3,TLSv1.2 -Dhttps.protocols=TLSv1.3,TLSv1.2 -jar app.jar
```

**代码：**

```java
SSLContext ctx = SSLContext.getInstance("TLS");   // 不要写 "TLSv1"
ctx.init(null, null, null);                         // 默认信任库，校验保持开启
```

**Spring Boot（application.properties）：**

```properties
server.ssl.enabled-protocols=TLSv1.3,TLSv1.2
```

**Windows 服务：** JVM 参数一般在服务包装程序的配置里（Tomcat 是 `tomcat9w.exe` 的 Java 选项卡，或服务的 `JAVA_OPTS`），把同样的 `-D` 参数加进去。

**对方系统确实只支持 TLS 1.0：** 正确做法是升级对方系统。作为临时的、范围受限的例外，只给这一个 JVM 加一个覆盖文件：

```properties
# /opt/app/legacy-tls.security
# 从你的 JDK 的 java.security 里原样复制 jdk.tls.disabledAlgorithms 的值，
# 只删掉其中的 "TLSv1," 和 "TLSv1.1,"，其余条目全部保留。
jdk.tls.disabledAlgorithms=SSLv3, DTLSv1.0, RC4, DES, <...你的 JDK 列表的其余部分...>
```

```bash
java -Djava.security.properties=/opt/app/legacy-tls.security -jar app.jar
```

只写一个 `=` 时，这个文件追加到主 `java.security` 之后，并对自己定义的键生效，而且只影响这个进程。

**国内常见场景：** 对接多年未升级的老系统、老版本数据库或前置机时最容易遇到，往往是当年为了兼容对方写死了 TLSv1。先确认对方现在是否已支持 TLS 1.2（用下面的 `openssl s_client -tls1_2` 测一下），很多时候对方早就支持了，只需要删掉自己代码里的旧配置。

## 不推荐：关闭证书校验

关闭证书校验对这个错误没有用，因为握手还没开始。安装“信任所有证书”的 `TrustManager`、设置永远返回 `true` 的 `HostnameVerifier`、往 `cacerts` 里随便导入证书，只会让应用接受伪造证书，把账号密码和数据暴露给链路上的任何人。

也不要为了去掉 `TLSv1` 而修改 JDK 全局的 `java.security`，那样这台机器上所有 Java 应用都会重新接受已废弃的协议。更安全的做法依次是：删掉写死的协议；推动对方升级到 TLS 1.2+；实在避免不了，才用只作用于单个 JVM、写明删除日期的覆盖文件。

## 怎么确认已经修好

修好的标准是：用和应用相同的 JDK 和 JVM 参数运行下面的程序，输出 `TLSv1.3` 或 `TLSv1.2`。

```java
// TlsCheck.java —— JDK 11+ 可直接运行：java TlsCheck.java example.com
import javax.net.ssl.*;

public class TlsCheck {
    public static void main(String[] args) throws Exception {
        SSLSocketFactory f = SSLContext.getDefault().getSocketFactory();
        try (SSLSocket s = (SSLSocket) f.createSocket(args[0], 443)) {
            SSLParameters p = s.getSSLParameters();
            p.setEndpointIdentificationAlgorithm("HTTPS");   // 保持域名校验
            s.setSSLParameters(p);
            s.startHandshake();
            System.out.println(s.getSession().getProtocol() + " " + s.getSession().getCipherSuite());
        }
    }
}
```

这个程序不依赖第三方库，适合在服务器上快速验证，输出里还会带上协商出的加密套件名。`openssl s_client -connect example.com:443 -servername example.com -tls1_2` 可以在 Java 之外确认服务器接受 TLS 1.2。公网服务器支持哪些版本可以用 [SSL 检测工具](https://ssl.sh/zh/tools/ssl-checker/)查看；如果对端归你管，用 [TLS 配置生成器](https://ssl.sh/zh/tools/tls-config-generator/)生成现代配置。握手能继续往下走、但又报证书错误时，接着看 [PKIX path building failed](https://ssl.sh/zh/errors/java-pkix-path-building-failed/)。
