# x509: certificate is valid for X, not Y（Go）怎么修

> Go 的 crypto/x509 提示证书的 SAN 里没有你连接的主机名。应修正访问域名、ServerName 或默认证书，而不是设置 InsecureSkipVerify。

Source: https://ssl.sh/zh/errors/go-x509-certificate-is-valid-for-not/
Last updated: 2026-11-10

**`x509: certificate is valid for www.example.com, not example.com` 的意思是：服务器证书覆盖的是 “not” 前面的那些名字，而你连接的主机（“not” 后面那个）不在其中。** Go 已经把证书里有哪些名字直接告诉你了。改用其中一个名字连接，发送正确的 ServerName，或者换一张包含你这个主机名的证书。`InsecureSkipVerify` 不是解决办法。

## 这条报错是什么意思

Go 的 TLS 客户端校验服务器时，会把 URL 里的主机名（或 `tls.Config.ServerName`）和证书的使用者可选名称（SAN）比对。对不上时，`crypto/x509` 返回 `HostnameError`；从 Go 1.20 起，TLS 层再把它包装成 `CertificateVerificationError`。用 `net/http` 时完整的一行是：

```text
Get "https://example.com/": tls: failed to verify certificate: x509: certificate is valid for www.example.com, not example.com
```

根据情况不同，`HostnameError` 的文字有几种，都直接来自 Go 的 `verify.go`：

- `x509: certificate is valid for A, B, not C`：有 DNS 类型的 SAN，但都不匹配。
- `x509: certificate is valid for 203.0.113.5, not 203.0.113.10`：你用 IP 连接，证书里的 IP SAN 是别的地址。
- `x509: cannot validate certificate for 203.0.113.10 because it doesn't contain any IP SANs`：你用 IP 连接，证书里没有任何 IP SAN。
- `x509: certificate is not valid for any names, but wanted to match example.com`：证书完全没有 SAN。
- `x509: certificate relies on legacy Common Name field, use SANs instead`：名字只写在 CN 里，见 [legacy Common Name](https://ssl.sh/zh/errors/go-x509-certificate-relies-on-legacy-common-name/)。

在 Linux 上，Go 自己做证书校验，会先检查主机名、再构建证书链。所以代理或 Ingress 返回的自签名默认证书，常常表现为这条域名错误，而不是 [signed by unknown authority](https://ssl.sh/zh/errors/x509-certificate-signed-by-unknown-authority/)。

读这条报错有个小技巧：先看 “not” 前面列出的名字属于谁。如果是你自己的域名、只是少了一个变体（比如只有 www），那是证书覆盖范围的问题；如果是 `ingress.local`、云厂商的域名或者一个完全陌生的网站，那就是请求被别的证书“接住”了，应该去查 Ingress、负载均衡、CDN 或 DNS；如果 “not” 后面是一个 IP，那就是用 IP 连接却没有设置 ServerName。按这三类分流，能省掉大部分排查时间。

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

最常见的原因是访问的名字不在证书覆盖范围内，比如把裸域名和 `www` 搞混。

### 1. 访问的名字不在证书覆盖范围内

裸域名和 `www` 搞混、用了旧的别名，或者通配符用深了一级（`*.example.com` 匹配 `api.example.com`，不匹配 `example.com` 和 `v1.api.example.com`）。

- **确认：**

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

- **修复：** 改用证书里列出的名字，或者重新签发包含所有域名的证书。

### 2. 代理或 Ingress 返回了默认证书

ingress-nginx 在找不到匹配的 TLS Secret 时，会返回一张主机名为 `ingress.local` 的自签名兜底证书；其他负载均衡和 CDN 对不认识的主机名也会返回自己的默认证书。这时报错会写 `valid for ingress.local, not example.com`，或者列出云厂商的域名。

- **确认：** `kubectl get ingress -A -o wide`，检查 Ingress 是否有包含你域名的 `tls` 配置，引用的 Secret 是否存在。
- **修复：** 补上 TLS 配置和 Secret；如果是 CDN、云负载均衡，就在控制台上传这个域名的证书。

### 3. 用 IP 或其他主机名连接，却没设置 ServerName

自定义拨号函数、服务发现返回 IP、`http.Transport` 写死了连接地址，Go 都会按它“认为”的主机名去校验。没有设置 `ServerName` 时，连接 IP 不会发送 SNI，托管多个站点的服务器还可能返回错误的证书。

- **修复：** 设置 `ServerName`（代码见下文），或者用证书里有的域名访问服务。

### 4. 内部服务的证书 SAN 不全

私有 CA 签发的证书常常只写了 `api.internal`，客户端却用 `api.internal.example.com`、Kubernetes 服务名或 Docker 容器名访问。签发证书时把客户端实际会用到的名字和 IP 都写进去。

### 5. 应答的不是目标服务器

报错里的名字属于一个完全无关的网站，说明 DNS、`/etc/hosts`、认证页面或过滤代理把你引到了别处。用 `nslookup example.com` 检查解析结果是否是服务应在的位置。

## 分平台修复命令

各平台的修法都是让连接用的主机名和证书 SAN 对上：改用证书里的名字、设置 `ServerName`，或者重签证书。

**任意系统，用正确的名字测试某个具体 IP（SNI 和校验都按域名）：**

```bash
curl -v --resolve example.com:443:203.0.113.10 https://example.com/
```

**Go 代码，连接 IP 但按真实域名校验：**

```go
tr := &http.Transport{
    TLSClientConfig: &tls.Config{ServerName: "api.example.com"},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://203.0.113.10/health")
```

**Kubernetes ingress-nginx：**

```yaml
spec:
  tls:
    - hosts: ["example.com", "www.example.com"]
      secretName: example-com-tls
  rules:
    - host: example.com
```

**Windows：** Windows 上的 Go 用系统校验器验证证书链，但域名规则相同；可以用 `certutil -dump cert.cer` 或[证书解析工具](https://ssl.sh/zh/tools/certificate-decoder/)查看证书。

**国内拉取依赖：** `go mod download` 访问默认代理报这个错，多半是应答方不是真正的代理。执行 `go env -w GOPROXY=https://goproxy.cn,direct` 换成国内可访问的模块代理，而不是用 `GOINSECURE` 或关闭校验绕过去。

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

`tls.Config{InsecureSkipVerify: true}` 会让 Go 接受任何证书、任何主机名。Go 文档明确写着：这种模式下，除非另做自定义校验，TLS 容易受到中间人攻击，只应该用于测试，或者和 `VerifyConnection`、`VerifyPeerCertificate` 配合使用。放到生产环境，链路上的任何人都能冒充你的 API 或镜像仓库，读走凭据。

更安全的替代办法：改正主机名；设置 `ServerName`；修好 Ingress 或 CDN 上的证书；签发 SAN 完整的证书；私有 CA 就把根证书加进 `RootCAs` 或系统证书池（Unix 上可以用 `SSL_CERT_FILE` / `SSL_CERT_DIR`）。

## 怎么确认已经修好

修好的标准是下面的拨号程序不报错，并打印出包含你所用主机名的列表。

```go
conn, err := tls.Dial("tcp", "example.com:443", &tls.Config{})
if err != nil { log.Fatal(err) }
fmt.Println(conn.ConnectionState().PeerCertificates[0].DNSNames)
conn.Close()
```

如果你改的是 Ingress 或 CDN 上的证书，生效可能需要几分钟，期间可以用上面的 `curl --resolve` 命令直接对每个节点的 IP 分别测试，确认所有节点都已经换上新证书，而不是只有一部分。再用 [SSL 检测工具](https://ssl.sh/zh/tools/ssl-checker/)从外部确认；规划新证书要写哪些名字时，可以先了解[使用者可选名称（SAN）](https://ssl.sh/zh/glossary/san/)。
