# DEPTH_ZERO_SELF_SIGNED_CERT：Node.js 正确信任自签名证书

> Node.js 报 self-signed certificate，说明服务器自己的证书是自签名的。这页讲怎样用 mkcert 本地 CA 或 ca 选项只信任这一张证书，以及为什么不能关校验。

Source: https://ssl.sh/zh/errors/node-depth-zero-self-signed-cert/
Last updated: 2026-11-10

**`self-signed certificate`（`DEPTH_ZERO_SELF_SIGNED_CERT`）的意思是：服务器出示的证书是自己签自己的，Node.js 没有任何理由信任它。** 本地 HTTPS 服务和很多内部工具都会这样，正确做法是告诉 Node.js 信任这张证书或签发它的 CA，而不是关掉校验。

## 快速修复

开发环境最省事的做法是用 [mkcert](https://github.com/FiloSottile/mkcert) 创建本地 CA，再用它签发证书，然后让 Node.js 信任这个 CA：

```bash
mkcert -install
mkcert localhost 127.0.0.1 ::1
export NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem"
node app.js
```

对于不归你管的单个内部服务（比如公司的 NAS、测试服务器），先取下它的证书，和负责人核对指纹，再作为 `ca` 传入：

```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/nas-example.pem")],
});
https.get("https://nas.example.com/", { agent }, (res) => console.log(res.statusCode));
```

## 原因

最常见的是开发或内部服务器本来就用自签名证书；其次是服务器返回了默认证书，或者网络上有设备拦截。

### 1. 开发或内部服务器用的是自签名证书

各种框架、测试容器、路由器、NAS、管理后台，首次启动时经常自动生成一张[自签名证书](https://ssl.sh/zh/glossary/self-signed-certificate/)。

- **确认：** `openssl s_client -connect host:443 -servername host </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer` 显示 subject 和 issuer 完全相同。
- **修复：** 换成内部 CA 签发的证书，或者按上面的方法显式信任。用[自签名证书生成器](https://ssl.sh/zh/tools/self-signed-certificate-generator/)可以生成带正确 SAN 的证书。

### 2. 响应的是错误的虚拟主机

请求的域名和服务器上配置的站点都对不上时（比如用 IP 访问、没带 SNI），Web 服务器会出示默认证书，常常是自签名的 “snakeoil” 证书。

- **确认：** subject 是 `localhost` 或机器名这类通用名字，不是你的域名。
- **修复：** 用正确的主机名连接；自己写 TLS 连接代码时设置 `servername` 发送 SNI。在服务器端把正确的站点设为该端口的默认站点。

### 3. 网络上有拦截

公共 Wi-Fi 的[认证页](https://ssl.sh/zh/errors/captive-portal-ssl-error/)、某些安全设备无法完成解密时，会出示自己的自签名证书。

- **确认：** issuer 是某个网络设备或厂商名，只在某个网络下报错。
- **修复：** 联系网络管理员；如果是公司代理，应该信任它的 CA，而不是一张自签名的站点证书。

### 4. 信任之后又报主机名不匹配

信任证书后如果改报 `ERR_TLS_CERT_ALTNAME_INVALID`，说明证书的 [SAN](https://ssl.sh/zh/glossary/san/) 里没有你访问的主机名，用正确的名字重新生成。

## 各环境修法

在开发框架、Docker 和 Electron 里，都用 `ca` 选项或 `NODE_EXTRA_CA_CERTS` 信任指定的证书或 CA。

**Vite、Next.js、webpack-dev-server 本地开发：** 用 mkcert 生成证书，服务端代码要调用开发服务器时设置 `NODE_EXTRA_CA_CERTS`。

**Docker Compose：** 挂载 CA 文件，在服务的 `environment` 里设置 `NODE_EXTRA_CA_CERTS`。

**Axios / node-fetch / undici：** Axios 支持传 `httpsAgent`；基于 undici 的 `fetch` 使用 Node.js 默认信任库，设置 `NODE_EXTRA_CA_CERTS` 最简单；直接用 undici 时给 `Agent` 传 `connect: { ca }`。

**Electron：** 应用代码用 `ca` 选项或 `NODE_EXTRA_CA_CERTS`；Chromium 渲染进程走操作系统证书库。

**对接内网系统（OA、ERP、政企内网接口）：** 向对方要根证书或证书指纹，按上面方法信任，并在代码注释里写明证书来源和到期时间，方便以后更换。

## 不要这样做

下面 3 种做法会让一批连接失去校验，而不只是报错的那一个。

- **`NODE_TLS_REJECT_UNAUTHORIZED=0`。** 整个进程的所有连接都不再校验，而且很容易留在 `.env` 文件里被带到生产环境。
- **在公共 HTTP 客户端里默认 `rejectUnauthorized: false`。** 调用公网 API 时问题也被一起掩盖了。
- **“内网主机一律接受自签名证书”。** 内网并不天然可信，应该只信任特定的证书或 CA。
