# unable to verify the first certificate：Node.js 缺中间证书

> Node.js 报 UNABLE_TO_VERIFY_LEAF_SIGNATURE，几乎都是服务器没发中间证书。补全服务器证书链，或用 NODE_EXTRA_CA_CERTS 补充证书，不要关校验。

Source: https://ssl.sh/zh/errors/node-unable-to-verify-leaf-signature/
Last updated: 2026-11-10

**`unable to verify the first certificate` 的意思是：Node.js 收到了服务器证书，却找不到签发它的那张 CA 证书，验证在第一步就失败了。** 最常见的原因是服务器没有发送中间证书。

## 快速修复

修法是让服务器发送完整证书链；服务器不归你管时，用 `NODE_EXTRA_CA_CERTS` 补上缺失的证书。先看服务器发出的证书链：

```bash
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null | grep -E "^ *[0-9] s:|Verify return"
```

如果只有 `0 s:`，并且显示 `Verify return code: 21 (unable to verify the first certificate)`，就去修服务器：发送站点证书加中间证书（Nginx 用 `ssl_certificate fullchain.pem;`）。用[证书链补全工具](https://ssl.sh/zh/tools/chain-builder/)可以生成正确的文件。

如果服务器不归你管（比如对接第三方接口），在启动时给 Node.js 补上缺失的中间证书或私有根证书：

```bash
export NODE_EXTRA_CA_CERTS=/etc/ssl/extra/example-intermediate.pem
node app.js
```

## 原因

最常见的是服务器没发中间证书，其余 3 种是链的顺序错、代码自带 `ca` 选项和企业代理。

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

- **确认：** 上面的 `openssl s_client` 只列出一张证书；[SSL 检测工具](https://ssl.sh/zh/tools/ssl-checker/)提示证书链不完整。
- **修复：** 部署完整证书链。Let's Encrypt 用 `fullchain.pem`；商业证书把站点证书和 CA 提供的中间证书包按“站点证书在前”拼在一起。阿里云、腾讯云下载的 Nginx 格式证书一般已包含中间证书，宝塔里“证书(PEM格式)”一栏要粘贴完整内容。见[缺少中间证书](https://ssl.sh/zh/errors/missing-intermediate-certificate/)。

### 2. 中间证书顺序错或者发错了

有的服务器发的是旧链路的中间证书，或者顺序颠倒。

- **确认：** 第 0 张证书的签发者（issuer）和第 1 张证书的主体（subject）对不上。
- **修复：** 从 CA 官网取当前的中间证书重新拼接。

### 3. 代码里自己传了 `ca`

在 `https.Agent` 或 `tls.connect` 里设置 `ca` 选项后，Node.js 只信任这个列表，不再使用内置的根证书。

- **确认：** 在代码里搜 `ca:` 或自定义的 `Agent`。
- **修复：** 把需要的根证书都放进这个列表，或者改用 `NODE_EXTRA_CA_CERTS`，保留默认根证书。

### 4. 企业代理只发了站点证书

部分上网行为管理设备生成的证书由一张它从不发送的中间证书签发。

- **修复：** 向 IT 要代理的完整 CA 链，用 `NODE_EXTRA_CA_CERTS` 加进来。

## 各环境修法

不管在代码、Docker 还是云函数里，都是把缺的证书加进信任列表，同时保留内置根证书。

**单个请求（你自己的代码）：**

```js
const fs = require("node:fs");
const https = require("node:https");
const tls = require("node:tls");

const agent = new https.Agent({
  ca: [...tls.rootCertificates, fs.readFileSync("/etc/ssl/extra/company-root.pem")],
});
https.get("https://api.example.com/", { agent }, (res) => console.log(res.statusCode));
```

**使用操作系统证书库：** `node --use-openssl-ca` 让 Node.js 改用 OpenSSL 的默认 CA 库（大多数 Linux 发行版就是系统证书包），而不是内置列表。较新的 Node.js 还提供 `--use-system-ca`，以 `node --help` 的输出为准。

**Docker：** 把证书打进镜像，再设置环境变量：

```dockerfile
COPY company-root.pem /usr/local/share/ca-certificates/company-root.crt
ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/company-root.crt
```

**Electron、云函数（阿里云函数计算、腾讯云 SCF 等）：** 在启动环境或函数配置里设置 `NODE_EXTRA_CA_CERTS`，程序启动后再改不会生效。

**npm、yarn、pnpm** 都运行在 Node.js 上，会报同一类错误，见 [npm unable to get local issuer certificate](https://ssl.sh/zh/errors/npm-unable-to-get-local-issuer-certificate/)。

## 不要这样做

关闭校验和固定站点证书都不是修法：前者去掉了保护，后者在下次续期时失效。

- **`NODE_TLS_REJECT_UNAUTHORIZED=0`。** 它会关掉进程里所有 TLS 连接的证书校验，包括调用支付、登录接口的请求。Node.js 设置后会打印警告。
- **在公共 HTTP 客户端里写 `rejectUnauthorized: false`。** 所有复用这个客户端的调用都会悄悄失去保护。
- **把站点证书本身当成“CA”固定下来。** 下次证书续期就失效；证书有效期还在不断缩短，续期只会越来越频繁。
