Nginx 明明已经换证书,浏览器却提示域名不匹配,甚至拿到了另一个站点的证书。先别急着重新签发:证书文件可能没错,错的是 TLS 握手选中了谁。本文针对同一 IP 承载多个 HTTPS 站点的场景,用 OpenSSL 和 curl 把连接地址、SNI、证书名称校验分开,定位“证书串站”。
一、先分清三个名字,别让 Host 背锅
我会先把问题拆成三项:TCP 连到哪个 IP;TLS 握手请求哪个域名;客户端准备验证哪个域名。它们常常相同,但排查时必须能独立指定。
SNI 是 ClientHello 中的服务器名称提示,服务端可以据此选证书。HTTP 的 Host 则在 TLS 握手之后发送。拿着错误证书时,只改 Host 就像已经上错车,却想靠换收件地址把车开回去。
因此,curl https://IP -H 'Host: 域名' 不能替代一次正确的域名 HTTPS 请求。也不要加 -k 后看到网页正常,就认定证书已经修好。

二、固定 IP,再明确发送 SNI
以下使用 OpenSSL 1.1.1/3.x 的常见选项。域名和 192.0.2.10 都是文档示例,必须替换为自己获授权排查的站点与实际入口。不要拿示例地址的超时当故障证据。
openssl s_client -connect 192.0.2.10:443 \\
-servername app.example.com -showcerts </dev/null
openssl s_client -connect 192.0.2.10:443 \\
-noservername -showcerts </dev/null
第一条模拟带目标名称的握手;第二条明确不发送 SNI,观察默认入口的行为。两次返回不同证书,通常说明名称选择在起作用,并不自动意味着配置错误。没有 SNI 时也可能被入口直接拒绝,不能要求它一定返回证书。
这里有个容易漏的细节:OpenSSL 从 1.1.1 起,-connect 后若是 DNS 名称,默认可能由它填充 SNI。所以“删掉 -servername”不等于“关闭 SNI”,做对照要用 -noservername。
三、发了 SNI,不等于验证了域名
-servername 解决“请给我哪一张”;-verify_hostname 解决“这张是否属于目标名称”。服务端即使不理会 SNI,仍可能完成 TLS 协商。
openssl s_client -connect 192.0.2.10:443 \\
-servername app.example.com \\
-verify_hostname app.example.com \\
-verify_return_error -brief </dev/null
校验依赖本机可信 CA 配置;内部 CA 场景应显式指定经过确认的 -CAfile,不要把随手下载的服务端证书直接当信任依据。-verify_return_error 让验证错误中止握手,避免诊断工具带着错误继续连接。
验收要同时检查验证输出与命令退出状态。出现 hostname mismatch 应查名称;出现 unable to get local issuer certificate,应先查证书链与信任来源。两种报错不能靠同一种“重启大法”解决。
四、用 curl 指定入口,不改变访问域名
curl –noproxy '*' –connect-timeout 5 –max-time 15 \\
–resolve app.example.com:443:192.0.2.10 \\
-I https://app.example.com/
–resolve 为这次请求提供指定的名称与端口解析结果;URL 仍保留域名,因此 SNI、证书名称检查和 HTTP Host 可以保持一致。这里禁用代理,是为了直接测目标入口;必须经过代理的网络应按实际架构另行测试。
-I 发的是 HEAD 请求,403 或 405 可能只是应用策略,不能把 HTTP 状态和 TLS 验证混为一谈。
五、回到 Nginx,只检查命中的配置
先运行 nginx -T 查看展开后的配置,重点看同一监听地址和端口下的 server_name、default_server、ssl_certificate。它会输出完整配置,可能含敏感参数,只在本机检查,不要直接贴到工单或公开文章。
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/nginx/tls/app/fullchain.pem;
ssl_certificate_key /etc/nginx/tls/app/privkey.pem;
# 保留本站原有的业务 location 配置
}
这是核对片段,不要覆盖原站点。检查旧目录、重复名称警告及实际接收请求的 Nginx。默认站点属于监听地址与端口,不是按文件名猜出来的。
确认文件和名称对应后,再执行 nginx -t && nginx -s reload。如果平台用服务管理器控制 Nginx,就采用对应的 reload 方式,并读回结果。配置测试通过,只说明配置能加载,不保证每个域名都拿到了正确证书。
六、用证书指纹定位,不凭文件时间猜
从第一条握手输出中复制第一段完整 PEM 证书,保存为 remote-leaf.pem。它是该连接返回的叶证书,不是私钥。再分别查看远端与配置文件:
openssl x509 -in remote-leaf.pem -noout \\
-subject -issuer -serial -dates -ext subjectAltName \\
-fingerprint -sha256
openssl x509 -in /etc/nginx/tls/app/fullchain.pem \\
-noout -fingerprint -sha256
相同 SHA-256 指纹可用于确认是同一张证书;不同则说明当前连接返回的不是这张本地叶证书。全链文件应以叶证书开头,不能拿中间证书参与比较。
域名覆盖重点看 SAN。通配符 *.example.com 不覆盖裸域 example.com,也不覆盖 a.b.example.com。如果服务配置了 RSA/ECDSA 双证书,客户端能力可能导致选中不同证书,应分别核对,不能把另一张合法证书直接判为串站。
七、按现象缩小范围
| 带正确 SNI 正常,不带时证书不同 | 客户端是否发送 SNI;默认入口是否符合预期 |
| 带 SNI 仍返回别站证书 | 监听入口、server_name、重复配置、证书路径 |
| 直连源站正常,公开域名异常 | CDN或负载均衡是否终止TLS、边缘证书是否更新 |
| 部分入口正常,部分错误 | 逐个地址检查部署版本,不只测一台节点 |
若 TLS 在 CDN 或负载均衡终止,用户看到的是那一层的证书。只更新源站文件,边缘证书不会因此自动变化。先画清实际连接路径,再决定在哪一层修复。
八、最后验收,不把“能打开”当结论
- 固定实际入口,带目标 SNI,记录返回证书 SAN 与指纹。
- 开启名称校验和验证失败中止,检查结果与退出状态。
- 用保留域名的 curl 请求复验,不使用跳过验证选项。
- 逐个已知入口检查;重新建立连接,避免只观察旧连接。
- 记录变更位置与回退版本,保存脱敏证据,不上传私钥。
排查不是多试几次 reload,而是证明“连到了谁、请求了谁、拿到了谁的证书”。
参考:Nginx HTTPS 与 SNI;OpenSSL s_client 选项;OpenSSL x509 证书检查;curl –resolve。



