# self-signed certificate in certificate chain 报错原因与修法

> 这个 OpenSSL 报错表示证书链顶端是一张本机不信任的自签名根证书，多半来自公司代理或内网 CA。正确做法是信任那张根证书，而不是关掉校验。

Source: https://ssl.sh/zh/errors/self-signed-certificate-in-certificate-chain/
Last updated: 2026-11-10

**`self-signed certificate in certificate chain` 的意思是：服务器的证书链最终落在一张自签名根证书上，而你的客户端不信任这张根证书。** 证书链本身是完整的，缺的是对顶端根证书的信任。在办公电脑上，几乎都是公司的 HTTPS 解密代理造成的；其他情况多是内网 CA 的根证书没有下发到客户端。

## 快速修复

修法是找到证书链顶端的根证书，核对后加进报错工具的信任库。先看顶端是哪张根证书：

```bash
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null \
  | grep -E '^ *[0-9] s:|^ *i:'
```

看最后一张证书：如果它的 subject 和 issuer 相同，并且写的是公司名、安全厂商名或内部 CA，那就是要信任的根证书。向 IT 部门要 PEM 格式的文件（或从系统证书库导出），核对指纹后，加到报错的工具里：

```bash
export NODE_EXTRA_CA_CERTS=/path/company-root.pem          # Node.js
npm config set cafile /path/ca-bundle.pem                 # npm
git config --global http.sslCAInfo /path/ca-bundle.pem    # Git
export REQUESTS_CA_BUNDLE=/path/ca-bundle.pem             # Python requests
curl --cacert /path/ca-bundle.pem https://example.com     # curl
```

npm、Git、curl 和 Python 的这些参数会“替换”默认 CA 列表，所以 `ca-bundle.pem` 应该是系统证书包再追加上公司根证书；`NODE_EXTRA_CA_CERTS` 则是在 Node 内置根证书基础上“追加”。

## 原因

办公电脑上最常见的原因是公司代理或杀毒软件解密 HTTPS，其次是内网 CA 没有下发到客户端。

### 1. 公司代理或杀毒软件解密 HTTPS

上网行为管理、安全网关以及部分杀毒软件的“HTTPS 扫描”功能，会解密流量后用自己根证书签发的证书重新加密。

- **确认：** 访问任何外部网站，链顶端都是同一张陌生的根证书；换到家里网络或手机热点后不再报错。
- **修复：** 按上面的方法逐个工具信任代理根证书，或者请 IT 部门把 registry.npmjs.org、pypi.org、国内镜像源等软件源加入解密白名单。

### 2. 内网 CA 没有下发到客户端

GitLab、Nexus、Harbor、内部 API 等服务常用公司自建的私有 CA，只有装了它根证书的机器才能验证通过。

- **确认：** 只有访问内网域名时报错，根证书的名称是公司内部 CA。
- **修复：** 通过系统信任库（`update-ca-certificates` 或 `update-ca-trust`）分发根证书，再给各个运行时单独配置。

### 3. 服务器发送了错误或过时的根证书

有些服务器把根证书也拼进了证书链。如果这张根证书是私有的、已过期的，或者与客户端信任的版本不同（交叉签名的旧版本），客户端可能就在这里停下。

- **确认：** 用[证书解析工具](https://ssl.sh/zh/tools/certificate-decoder/)查看，服务器发出的链里包含根证书，而其他客户端在没有它时也能正常访问。
- **修复：** 服务器只发送站点证书和中间证书，可以用[证书链补全工具](https://ssl.sh/zh/tools/chain-builder/)重新生成证书链。在宝塔或云厂商控制台粘贴证书时，注意不要把根证书也贴进去。

### 4. 开发环境的本地 CA（mkcert、本地 Kubernetes、测试环境）

本地 CA 安装后浏览器会信任，但自带 CA 列表的运行时不会。

- **修复：** 用 `NODE_EXTRA_CA_CERTS` 等方式显式加载开发根证书，不要把它带进生产镜像。

## 各环境修法

每个工具都要单独信任这张根证书，区别只在信任库的位置和命令。

**Linux 系统信任库（Debian、Ubuntu、Alpine）：**

```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`。

**Windows：** Git for Windows 可以执行 `git config --global http.sslBackend schannel` 改用 Windows 证书库，自动信任域策略下发的根证书。Node.js 和 Python requests 仍需要上面的单独配置。

**macOS：** 把根证书导入“系统”钥匙串并设为始终信任，Node、npm、Python 仍要分别配置。

**Java：** 把根证书导入 JVM 信任库，见 [PKIX path building failed](https://ssl.sh/zh/errors/java-pkix-path-building-failed/)。

**Docker 构建：** 在任何 `npm install`、`pip install` 之前把根证书复制进镜像并执行 `update-ca-certificates`，运行 Node 的地方再设置 `NODE_EXTRA_CA_CERTS`。如果你已经把 npm 源换成了 npmmirror 等国内镜像，经过公司代理时同样会遇到这个报错，换源并不能绕过它。

## 不要这样做

关闭校验能消除报错，但对应工具从此不再验证任何服务器的身份。

- **`NODE_TLS_REJECT_UNAUTHORIZED=0`。** Node 会打印警告，因为它关闭的是整个进程所有 TLS 连接的校验。
- **`npm config set strict-ssl false`。** 之后下载软件包时完全不验证服务器身份，这正是供应链投毒常用的入口。
- **`git config --global http.sslVerify false`。** 它对所有远程仓库生效，不只是报错的那一个。
- **`verify=False` 或 `curl -k`。** 同样的问题：失去的是全部保护，而不只是报错的那一项检查。
