编程语言
x509: certificate is valid for X, not Y(Go)怎么修
x509: certificate is valid for www.example.com, not example.comx509: 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 时完整的一行是:
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。
在 Linux 上,Go 自己做证书校验,会先检查主机名、再构建证书链。所以代理或 Ingress 返回的自签名默认证书,常常表现为这条域名错误,而不是 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)。
- 确认:
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 和校验都按域名):
curl -v --resolve example.com:443:203.0.113.10 https://example.com/
Go 代码,连接 IP 但按真实域名校验:
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:
spec:
tls:
- hosts: ["example.com", "www.example.com"]
secretName: example-com-tls
rules:
- host: example.com
Windows: Windows 上的 Go 用系统校验器验证证书链,但域名规则相同;可以用 certutil -dump cert.cer 或证书解析工具查看证书。
国内拉取依赖: 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)。
怎么确认已经修好
修好的标准是下面的拨号程序不报错,并打印出包含你所用主机名的列表。
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 检测工具从外部确认;规划新证书要写哪些名字时,可以先了解使用者可选名称(SAN)。
常见问题
为什么别的工具能用的证书,Go 却拒绝?
Go 只拿主机名去比对 SAN 扩展。从 Go 1.15 起,旧式的 Common Name 被忽略,名字只写在 CN 里的证书会失败;这种情况有自己的报错,见 certificate relies on legacy Common Name。
报错里出现 ingress.local 是怎么回事?
这是 ingress-nginx 在找不到匹配的 TLS Secret 时使用的自签名兜底证书上的主机名。说明 Ingress 没有为你的域名配置 tls、Secret 名字写错了,或者请求的域名和 Ingress 规则里的不一致。把 Ingress 修好,报错就会消失。
怎么连接 IP,但按真实域名校验?
在 Transport 上设置 tls.Config{ServerName: "api.example.com"}。Go 会把 api.example.com 作为 SNI 发出,并按这个名字校验证书,而 TCP 连接仍然发往你指定的 IP。
go mod download 报 certificate is valid for ..., not proxy.golang.org,为什么?
说明应答的不是真正的模块代理,可能是认证页面、过滤代理,或者 DNS 解析到了别处。先用 nslookup proxy.golang.org 和证书主题核对。默认代理访问不了的网络,可以把 GOPROXY 设为 https://goproxy.cn,direct 这样可访问的国内模块代理,而不是关闭校验。