最准确的验证方式是用 openssl s_client 模拟 TLS 握手:执行 openssl s_client -connect your.ip:443 -servername site-a.com -showcerts 2>/dev/null | openssl x509 -noout -subject,输出应匹配该域名的 CN 或 SAN;换域名测试若返回相同证书,说明 SNI 未生效,常见原因是多个 server 块复用 ssl_certificate 路径或缺少 listen 443 ssl 显式声明。

直接用 openssl s_client 模拟 TLS 握手,是最准确、最底层的测试方式。它绕过浏览器缓存和中间代理,能真实反映 Nginx 是否根据 server_name 正确加载并返回对应证书。
用 openssl s_client 验证域名与证书是否匹配
在服务器或可访问该 IP 的客户端上执行:
-
openssl s_client -connect your.ip:443 -servername site-a.com -showcerts 2>/dev/null | openssl x509 -noout -subject应输出类似subject=CN = site-a.com或包含该域名的 SAN 信息 - 换另一个域名再试:
openssl s_client -connect your.ip:443 -servername site-b.net -showcerts 2>/dev/null | openssl x509 -noout -subject输出必须是site-b.net对应的证书主体,不能和上一条一样 - 若两次都返回同一张证书,说明 SNI 未生效——常见原因是多个
server块共用了相同的ssl_certificate路径,Nginx 只加载第一个块的证书
检查 Nginx 配置是否满足 SNI 协同前提
server_name 不是孤立起作用的,它必须和证书、监听配置形成闭环:
- 每个
server块必须显式写listen 443 ssl(不能只写listen 443) -
server_name列出的域名,必须全部出现在对应证书的 SAN(Subject Alternative Name)中;例如证书只含api.example.com,就不能用server_name example.com去匹配 - 禁止复用证书路径:哪怕两个域名证书物理文件相同,也要在各自
server块里重复写全路径,如ssl_certificate /etc/letsencrypt/live/site-a.com/fullchain.pem
观察 Nginx 错误日志确认匹配行为
开启 debug 级日志后,Nginx 会在 TLS 握手阶段记录 SNI 匹配细节:
- 临时启用:
error_log /var/log/nginx/error.log debug;,然后nginx -s reload - 发起一次 HTTPS 请求后,搜索日志:
grep "client sent server name" /var/log/nginx/error.log正常会看到类似:client sent server name "site-a.com" - 接着看后续行是否有
SSL certificate hash或using certificate字样,确认加载的是哪个证书文件
模拟无 SNI 的老旧客户端行为
某些 IoT 设备或旧系统不发 SNI,此时 Nginx 默认使用配置中第一个 server 块(按文件顺序)响应:
- 测试命令去掉
-servername参数:openssl s_client -connect your.ip:443 -showcerts 2>/dev/null | openssl x509 -noout -subject - 结果应是你期望的“兜底证书”,比如主站或默认站点的证书;如果不是,说明配置顺序或
default_server设置有误 - 如需显式控制,默认块可加
listen 443 ssl default_server;,避免依赖加载顺序


















