# ERR_TLS_CERT_ALTNAME_INVALID：Node.js 证书域名不匹配

> ERR_TLS_CERT_ALTNAME_INVALID 表示服务器证书里没有你访问的域名或 IP。应修正访问地址、SNI 或证书，而不是设置 rejectUnauthorized: false。

Source: https://ssl.sh/zh/errors/node-err-tls-cert-altname-invalid/
Last updated: 2026-11-10

**`ERR_TLS_CERT_ALTNAME_INVALID` 的意思是：你的 Node.js 代码连接的域名或 IP，不在服务器证书的“使用者可选名称”（SAN）扩展里。** 证书可能是可信的、也没过期，只是它属于别的名字。要修的是访问地址、SNI 名称或者证书本身，而不是关闭校验。

## 这条报错是什么意思

这条报错说明证书可信，但证书里的名字和你连接的地址对不上。证书链校验通过之后，Node.js 会调用 `tls.checkServerIdentity(hostname, cert)` 比对请求的主机名和证书。全都对不上时，就抛出 `code` 为 `'ERR_TLS_CERT_ALTNAME_INVALID'` 的错误，附带 `reason`、`host`、`cert` 属性，消息类似：

```text
Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames: Host: example.com. is not in the cert's altnames: DNS:www.example.com
```

冒号后面那段就是 `reason`，Node.js 会给出这几种：

- `Host: <域名>. is not in the cert's altnames: DNS:...`：证书里的 DNS 名称不包含这个主机名。
- `IP: <地址> is not in the cert's list: ...`：你用 IP 访问，但证书里没有这个 IP。
- `Host: <域名>. is not cert's CN: ...`：证书没有 DNS 类型的 SAN，Node.js 退回去比对 Common Name，也没对上。
- `Cert does not contain a DNS name`：既没有 DNS SAN，也没有 Common Name 可比。

浏览器里对应的报错是 [NET::ERR_CERT_COMMON_NAME_INVALID](https://ssl.sh/zh/errors/net-err-cert-common-name-invalid/)，原理和修法相同。

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

最常见的是访问地址不在证书覆盖范围内，比如访问裸域名而证书只签了 www。

### 1. 访问地址不在证书覆盖范围内

最典型的是用 `https://example.com` 访问，而证书只签了 `www.example.com`；或者测试环境用了生产环境的证书。通配符还有一个限制：`*.example.com` 只覆盖一级，既不匹配 `example.com`，也不匹配 `a.b.example.com`。

- **确认：** 列出服务器实际出示的证书里有哪些名字：

```bash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -ext subjectAltName
```

也可以把证书粘贴到[证书解析工具](https://ssl.sh/zh/tools/certificate-decoder/)里查看。

- **修复：** 改用证书覆盖的名字，或者重新签发包含所有域名的证书，例如 `certbot certonly -d example.com -d www.example.com`。在阿里云、腾讯云等平台申请证书时，提交前先确认这张证书会覆盖哪些名字，裸域名和 www 是否都包含在内。

### 2. 没发正确的 SNI，服务器返回了默认证书

一台服务器托管多个网站时，会根据 ClientHello 里的 SNI 选择证书。客户端用 IP 连接、走隧道、或者自定义了 DNS 解析，却没有发送正确的 SNI，就会拿到默认站点的证书。

- **确认：** 用 `openssl s_client` 分别带和不带 `-servername` 连接，证书主题不同就说明是 SNI 在起作用。
- **修复：** 显式传入真实域名：

```javascript
const tls = require('node:tls');
const socket = tls.connect({ host: '203.0.113.10', port: 443, servername: 'example.com' }, () => {
  console.log('authorized:', socket.authorized);
  socket.end();
});
```

### 3. 直接用 IP 访问

`https://203.0.113.10/` 除非证书里有这个 IP，否则必然失败。通过 IP 访问的内网服务、Docker 或 Kubernetes 的服务名（`https://api:8443`）、`localhost` 都常遇到这个问题。

- **修复：** 用证书里有的域名访问；内网证书则把 IP 或服务名加进 SAN 列表重新签发。

### 4. CDN、负载均衡或云平台返回了平台自己的证书

自定义域名已经解析到 CDN、对象存储或云托管平台，但还没在控制台上传这个域名的证书，平台就会返回它自己的默认证书，SAN 里是平台的域名。国内用对象存储绑定自定义域名、CDN 加速域名时很常见。

- **确认：** SAN 列表里是云厂商的域名，而不是你的。
- **修复：** 在对应产品的控制台里为这个域名上传或申请证书，然后重新测试。

### 5. 新增域名后没更新证书

服务器还在用新增域名之前的旧证书。按完整的域名列表续期或重签，并重载服务器。用 certbot 的话，`certbot certificates` 可以列出每张证书包含的域名；新证书签好后别忘了重载 Nginx，否则进程里还是旧证书。负载均衡和 CDN 上的证书要单独更新，它们不会自动跟着源站变。

## 推荐的排查顺序

排查从报错里的 `reason` 开始，大多数情况 3 步就能定位。先看它是 `Host:` 开头还是 `IP:` 开头，后面列出的名字是不是你的域名。名字完全陌生（比如是云厂商或另一个网站的域名），优先怀疑 SNI 和默认证书；名字是你的，只是少了裸域名或 www，就是证书覆盖范围的问题；你访问的是 IP，就改成域名。然后用上面的 `openssl s_client` 命令带上 `-servername` 再看一次，确认服务器在正确 SNI 下出示的证书。

## 分平台修复命令

修复分两步：先查看服务器实际发来的证书，再按真实域名连接和校验。

**Linux / macOS（查看服务器发来的证书）：**

```bash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
```

**Windows PowerShell（用 Node.js 做同样的检查）：**

```powershell
node -e "const s=require('tls').connect(443,'example.com',{servername:'example.com'},()=>{console.log(s.getPeerCertificate().subjectaltname);s.end()})"
```

**连接 IP，但按真实域名校验**（校验完整保留）：

```javascript
const https = require('node:https');
const tls = require('node:tls');
https.get({
  host: '203.0.113.10',
  servername: 'internal.example.com',
  headers: { Host: 'internal.example.com' },
  checkServerIdentity: (_host, cert) => tls.checkServerIdentity('internal.example.com', cert),
}, res => console.log(res.statusCode));
```

**Nginx 托管多个站点（含宝塔面板）：** 确认每个 `server` 块的 `server_name` 和 `ssl_certificate` 对应正确，默认站点不会抢走你的域名。

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

`rejectUnauthorized: false`、`NODE_TLS_REJECT_UNAUTHORIZED=0`、`checkServerIdentity: () => undefined` 都能让报错消失，代价是失去对服务器身份的验证。前两个接受任何证书；第三个虽然保留了证书链校验，却去掉了域名检查，危害同样大：攻击者只要拥有任何一个域名的任何受信任证书，就能冒充你的服务器，读取 token、密码和接口数据。

安全的替代办法：用正确的域名访问；设置 `servername` 让 SNI 选中正确的证书；签发证书时把要用的名字都写进去；必须用 IP 连接时，像上面那样用 `tls.checkServerIdentity(expectedName, cert)` 按预期域名校验。

## 怎么确认已经修好

下面的命令输出状态码和 `true`，就说明证书校验已经通过。

```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.reason || e.message))"
```

应当输出状态码和 `true`。再把裸域名和 `www` 分别放进 [SSL 检测工具](https://ssl.sh/zh/tools/ssl-checker/)确认都能匹配；规划新证书要包含哪些名字时，可以先了解[使用者可选名称（SAN）](https://ssl.sh/zh/glossary/san/)。
