# PKIX path building failed（Java）原因与修复

> PKIX path building failed 表示 JVM 无法把服务器证书链到信任库里的根证书。多为缺中间证书、企业代理或私有 CA，应补证书链或用 keytool 导入根证书。

Source: https://ssl.sh/zh/errors/java-pkix-path-building-failed/
Last updated: 2026-11-10

**`PKIX path building failed ... unable to find valid certification path to requested target` 的意思是：JVM 没法把服务器证书连到它所加载的信任库里的任何一个根证书。** 最常见的情况是服务器没发中间证书、公司代理重新签发了 HTTPS 流量，或者内网 CA 从来没导入过 JDK 的 `cacerts`。

## 快速修复

服务器缺中间证书就到服务器上补链，JVM 不信任根证书才用 keytool 导入。先判断是哪一种，不要一上来就导证书：

```bash
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
```

- 输出里只有一张证书，结尾是 `Verify return code: 21 (unable to verify the first certificate)`：**服务器证书链不完整**，到服务器上配置完整证书链，见[缺少中间证书](https://ssl.sh/zh/errors/missing-intermediate-certificate/)。
- 证书链完整，但最上面的 `issuer` 是公司名、代理设备厂商或内网 CA：**JVM 不信任这张根证书**，导入到报错的那个 JVM 的信任库：

```bash
# JDK 9 及以后
keytool -importcert -cacerts -alias company-root -file company-root.pem -storepass changeit -noprompt
# JDK 8
keytool -importcert -keystore "$JAVA_HOME/jre/lib/security/cacerts" -alias company-root -file company-root.pem -storepass changeit -noprompt
```

导入后要重启应用，信任库只在启动时读一次。

## 原因

这个报错有 5 种原因，最常见的是服务器没发中间证书。

### 1. 服务器没发中间证书

Java 不会自己去下载缺失的中间证书，所以 Chrome 能打开的网站，Java 照样可能报错。

- **确认：** `openssl s_client -showcerts` 只显示站点证书，或者[证书链补全工具](https://ssl.sh/zh/tools/chain-builder/)提示缺少中间证书。
- **修复：** 让服务器发送站点证书加中间证书（Nginx 用 `fullchain.pem`，Tomcat、Apache、负载均衡上传完整链）。阿里云、腾讯云 SLB/CLB 上传证书时，“证书内容”要粘贴完整链。

### 2. 公司代理或安全网关解密了 HTTPS

不少企业的上网行为管理、安全网关会解密 HTTPS，再用自己的私有根证书重新签发。浏览器信任它，是因为 IT 已经把根证书推送到了系统证书库；但 Java 不读系统证书库。

- **确认：** 加 `-Djavax.net.debug=ssl:handshake` 运行，或在同一台机器上用 `openssl s_client` 看，`issuer` 是代理产品名或公司名。
- **修复：** 把代理根证书导入 cacerts；Windows 上也可以用 `-Djavax.net.ssl.trustStoreType=Windows-ROOT`。

### 3. 内网私有 CA 或自签名证书

内部接口、Nexus 私服、自建 GitLab 经常用公司自建 CA 签发的证书。

- **修复：** 每个 JVM 导入一次内网根证书（不是站点证书）；更好的做法是做一个专用信任库，让应用显式加载：

```bash
keytool -importcert -keystore app-truststore.p12 -storetype PKCS12 -storepass changeit -alias company-root -file company-root.pem -noprompt
java -Djavax.net.ssl.trustStore=app-truststore.p12 -Djavax.net.ssl.trustStorePassword=changeit -jar app.jar
```

注意 `javax.net.ssl.trustStore` 是**替换** cacerts，自定义信任库里也要包含应用需要访问的公网根证书，否则调用支付宝、微信支付等公网接口时又会报同样的错。

### 4. JDK 太旧，cacerts 没更新

很老的 JDK 版本里没有新的根证书，CA 切换到新根以后签发的证书就验证不过。

- **修复：** 升级到同一大版本的最新更新版，每次更新都会带上新的 cacerts。

### 5. 应用加载的不是你以为的那个信任库

Tomcat Connector、Spring 配置、Kafka 或 Elasticsearch 客户端都可能单独指定信任库。

- **确认：** 加 `-Djavax.net.debug=ssl:trustmanager`，日志会打印实际加载的信任库和其中的证书。

## 各环境修法

各环境的做法都是把根证书加进实际报错的那个 JVM 读取的信任库，位置因发行版和部署方式而异。

**Debian / Ubuntu 的 OpenJDK：** cacerts 由 `ca-certificates-java` 从系统证书库生成，往系统里加根证书就能同时更新 Java：

```bash
sudo cp company-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
```

**CentOS / RHEL / Rocky / 阿里云 Alibaba Cloud Linux：** 把根证书放到 `/etc/pki/ca-trust/source/anchors/`，执行 `sudo update-ca-trust`；系统 OpenJDK 读的是 `/etc/pki/ca-trust/extracted/java/cacerts`。

**Docker 镜像：** 在构建时就把根证书打进镜像，每个容器启动即可用：

```dockerfile
COPY company-root.pem /tmp/company-root.pem
RUN keytool -importcert -cacerts -alias company-root -file /tmp/company-root.pem -storepass changeit -noprompt
```

**Windows：** 用上面的 `Windows-ROOT`；或者以管理员身份运行 `keytool`，因为 `Program Files` 下的 cacerts 默认不可写。

**构建工具：** Maven、Gradle 下载依赖时也会报同样的错，见 [Maven PKIX path building failed](https://ssl.sh/zh/errors/maven-pkix-path-building-failed/)。

## 不要这样做

下面这些做法会关掉身份校验，或者让导入的证书很快失效。

- **写一个什么都信任的 `X509TrustManager`**（`checkServerTrusted` 为空），或者 `HostnameVerifier` 直接返回 `true`。这些从博客复制来的代码会关掉身份校验，而且往往一路带进生产环境。
- **导入站点证书代替根证书。** 只是把问题藏起来，下次续期又会报错。
- **删掉 cacerts 或用别的机器、旧 JDK 的文件覆盖。** 会丢失新的根证书，而且下次升级 JDK 时你的修改又会被覆盖。
