# UNABLE_TO_GET_ISSUER_CERT_LOCALLY：Node.js 找不到根证书

> Node.js 在自带的 CA 列表里找不到签发服务器证书链的根证书。用 NODE_EXTRA_CA_CERTS 或 --use-system-ca 加入根证书，不要关闭 TLS 校验。

Source: https://ssl.sh/zh/errors/node-unable-to-get-issuer-cert-locally/
Last updated: 2026-11-10

**`UNABLE_TO_GET_ISSUER_CERT_LOCALLY` 的意思是：Node.js 拿到了服务器的证书链，但在它信任的根证书里，找不到签发这条链顶端证书的 CA。** 实际情况往往是服务器没问题，只是 Node.js 不认识那张根证书：它只信任自己内置的一份 Mozilla CA 列表，不读取公司装在 Windows、macOS 或 Linux 系统里的根证书。正确做法是把这张根证书交给 Node.js，而不是关闭校验。

## 这条报错是什么意思

这条报错说明证书链的顶端连不到 Node.js 信任的任何根证书。它来自 OpenSSL 的证书链校验，到你的代码里是一个 `Error` 对象，`code` 为 `'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'`，消息是 `unable to get local issuer certificate`，例如：

```text
Error: unable to get local issuer certificate
    at TLSSocket.onConnectSecure (node:_tls_wrap:...)
  code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
```

这里的“local（本地）”指的是本地信任库。Node.js 先用服务器发来的证书拼出一条链，再到受信任的根证书里找最后一张证书的签发者。Node.js 文档说明，默认信任库是 Mozilla CA 列表的一份快照，在版本发布时固定，各平台完全相同。公司根证书、内网私有 CA、或者很新的公开根证书，只要不在这份列表里，Node.js 就不认识，除非你手动加进去。

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

最常见的是公司代理或杀毒软件解密了 HTTPS，其次是内网私有 CA 和代码里显式传了 `ca` 选项。

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

企业代理、上网行为管理、安全网关和杀毒软件的“HTTPS 扫描”会用自己的根证书重新签名流量。浏览器信任它，是因为 IT 已经把根证书装进了系统；Node.js 看不到系统证书库。

- **确认：** 查看你实际收到的证书是谁签发的：

```bash
openssl s_client -connect registry.npmjs.org:443 -servername registry.npmjs.org </dev/null 2>/dev/null | openssl x509 -noout -issuer
```

签发者是公司名称或安全厂商，而不是公开 CA，就可以确认。见[企业 SSL 解密导致的报错](https://ssl.sh/zh/errors/corporate-ssl-inspection/)。

- **修复：** 把这张根证书导出为 PEM，用 `NODE_EXTRA_CA_CERTS` 加入，或者使用 `--use-system-ca`。

### 2. 内网私有 CA

内部 API、自建 GitLab、Nexus、Harbor 等服务，证书常由公司自己的 CA 签发。证书链本身没问题，只是根证书是私有的。

- **确认：** 签发者是内网 CA；装了这张根证书的其他机器可以正常访问。
- **修复：** 通过 `NODE_EXTRA_CA_CERTS` 或系统证书库统一分发内网根证书。

### 3. 代码里显式传了 `ca` 选项

Node.js 文档写明：显式指定 `ca` 选项后，内置的根证书和额外证书都不再使用。为了访问某个内网服务而写了 `ca: fs.readFileSync('internal-root.pem')` 的代码，访问任何公网网站都会报这个错。

- **确认：** 搜索 HTTPS Agent、axios、got、undici 配置里的 `ca:`。
- **修复：** 在内置根证书的基础上追加，而不是替换：

```javascript
const tls = require('node:tls');
const fs = require('node:fs');
const ca = [...tls.rootCertificates, fs.readFileSync('/etc/ssl/company-root.pem', 'utf8')];
```

### 4. 服务器发的证书链不完整或不常规

服务器只发了部分中间证书，或者发来的链最终指向一个不在 Node.js 快照里的根证书，也会是同样的报错。用[证书链补全工具](https://ssl.sh/zh/tools/chain-builder/)看看服务器发了什么、完整的链应该是什么样。如果服务器只发了站点证书，更常见的报错是 [UNABLE_TO_VERIFY_LEAF_SIGNATURE](https://ssl.sh/zh/errors/node-unable-to-verify-leaf-signature/)。

- **修复：** 服务器必须发送完整证书链（Nginx 用 `fullchain.pem`；宝塔面板里粘贴证书时要包含中间证书）。

### 5. Node.js 版本太旧

内置列表只有升级 Node.js 才会更新。很老的版本可能缺少公开 CA 现在使用的根证书。用 `node -p "process.version"` 查看版本，升级到仍在维护的版本。

## 分平台修复命令

各平台的修法都是把缺的根证书交给 Node.js：用 `NODE_EXTRA_CA_CERTS` 追加，或者让 Node.js 读系统证书库。

**任意平台，不改代码：**

```bash
export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/company-root.pem
node app.js
```

**Windows 和 macOS，Node.js 22.15+ / 23.8+：** 直接使用操作系统证书库，域策略或 MDM 下发的根证书都能生效：

```bash
node --use-system-ca app.js
# Node.js 24.6+ / 22.19+ 也可以用环境变量
NODE_USE_SYSTEM_CA=1 node app.js
```

Windows PowerShell 里先执行 `$env:NODE_USE_SYSTEM_CA = "1"` 再启动 node。

**Debian / Ubuntu：** 把根证书装进系统，再让 Node.js 读取：

```bash
cp company-root.crt /usr/local/share/ca-certificates/ && update-ca-certificates
node --use-openssl-ca app.js      # 新版本也可用 --use-system-ca
```

**Docker：** 在镜像里复制根证书，并在 Dockerfile 里设置变量：

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

**国内开发环境：** 下载依赖慢可以把 registry 换成 npmmirror：`npm config set registry https://registry.npmmirror.com`。但换镜像只解决速度问题；如果公司网络在解密 HTTPS，访问镜像站同样会报这个错，仍然要按上面的方法加根证书。

## 从公司电脑导出代理根证书

如果确认是公司代理在解密 HTTPS，最省事的办法是直接向 IT 部门要 PEM 格式的根证书。拿不到时，也可以自己从系统里导出：Windows 上运行 `certmgr.msc`，在“受信任的根证书颁发机构”里找到代理厂商或公司名称的证书，右键导出为“Base64 编码 X.509（.CER）”，这种文件本身就是 PEM 格式；macOS 上打开“钥匙串访问”，在“系统”钥匙串里找到对应证书，导出为 `.pem`。导出后用 `openssl x509 -in company-root.pem -noout -subject -issuer` 检查一下，主题和签发者相同，说明拿到的确实是根证书。

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

`NODE_TLS_REJECT_UNAUTHORIZED=0`、`rejectUnauthorized: false`、`npm config set strict-ssl false` 都靠“接受任何证书”让报错消失，代价是失去对服务器身份的验证。Node.js 文档明确表示强烈不建议使用这个环境变量，Node.js 运行时也会打印警告，说明它会禁用证书校验、让 TLS 连接和 HTTPS 请求变得不安全。设置之后，恶意 Wi-Fi 或被入侵的代理可以冒充你的 API、包仓库或登录服务，截获 token，甚至在 `npm install` 时塞进被篡改的包。

更安全的替代办法：多一张根证书用 `NODE_EXTRA_CA_CERTS`；复用 IT 已部署的根证书用 `--use-system-ca`；代码里用 `ca: [...tls.rootCertificates, extraRoot]`；自己的服务器则配置完整证书链。

## 怎么确认已经修好

修好的标志是下面的命令输出状态码和 `true`，并且没有 NODE_TLS_REJECT_UNAUTHORIZED 警告。

```bash
node -e "require('https').get('https://example.com', r => { console.log(r.statusCode, r.socket.authorized); r.resume(); }).on('error', e => console.error(e.code, e.message))"
```

应当输出状态码和 `true`（连接已通过校验），没有 `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`，也没有 NODE_TLS_REJECT_UNAUTHORIZED 的警告。再用 `env | grep NODE_TLS` 确认没有任何地方残留这个变量。证书链是怎么拼起来的，见[证书链原理](https://ssl.sh/zh/learn/certificate-chains/)。
