SNI 是什么
SNI(Server Name Indication)是 RFC 6066 定义的 TLS 扩展:客户端在 ClientHello 里以明文写出自己要访问的主机名,服务端据此选择对应证书与虚拟主机配置。它让同一个 IP 地址能承载多个域名与多张证书。若客户端不发送 SNI,服务端只能回落到默认站点,拿到与目标域名不匹配的证书。
基本信息
| 标准 | RFC 6066 §3 |
|---|---|
| 传输形态 | 位于 ClientHello 中,TLS 1.3 下仍为明文 |
| 作用 | 同一 IP 部署多域名与多证书 |
| 缺失后果 | 服务端返回默认站点证书,客户端报域名不匹配 |
| 隐私考虑 | ECH 扩展用于加密该字段,仍在推进中 |
| 排查要点 | 测试时必须带上 -servername,否则结果不可信 |
典型使用场景
- 多域名共用一台服务器:一个 IP 承载多个站点,各自使用独立证书
- CDN 与负载均衡:边缘节点按 SNI 选择证书与回源策略
- 证书排查:用 IP 直连加 SNI 精准定位是证书问题还是解析问题
- 灰度新证书:同一域名下按 SNI 分发到不同证书做对比验证
- 默认站点设计:为未匹配域名准备合理默认站点,避免暴露错误证书
指定与不指定 SNI 的对比
指定与不指定 SNI 的对比
# 带 SNI:拿到目标域名的证书,这是正确的测试方式
openssl s_client -connect 203.0.113.10:443 -servername example.com </dev/null | grep 'subject='
# 不带 SNI:拿到默认站点证书,容易误判为证书配置错误
openssl s_client -connect 203.0.113.10:443 </dev/null | grep 'subject='
# 按域名正常访问,对照 DNS 与证书是否匹配
openssl s_client -connect example.com:443 -servername example.com </dev/null | grep -E 'subject=|issuer='
# curl 在 DNS 未生效时用 --resolve 保持 SNI 与域名校验
curl -sI --resolve example.com:443:203.0.113.10 https://example.com | head -3常见现象与原因
| 现象 | 原因 | 处置 |
|---|---|---|
| 报域名不匹配 | 未发送 SNI,命中默认站点证书 | 测试时加 -servername,客户端确认支持 SNI |
| 拿到其他站点证书 | 多域名共 IP 且默认站点配错 | 显式配置 default_server 并放一张中性证书 |
| 老客户端连接失败 | 早期 Java 与部分 IoT 库不发 SNI | 升级客户端,或为其单独规划 IP |
| 新域名上行后偶发失败 | 证书未覆盖全部 SAN,或 CDN 回源取错证书 | 核对 SAN 与边缘节点证书配置 |
| 日志里 Host 与证书不一致 | SNI 与 HTTP Host 头不一致被误配 | 检查是否存在域名跳转或改写规则 |
SNI 在 TLS 1.3 中依然是明文字段,抓包能看到目标域名,因此它不提供隐私保护。需要隐藏访问域名的场景要依赖 ECH(加密客户端问候)等演进方案,且需要服务端与客户端同时支持。
nginx 里若没有配置默认站点,未匹配的域名会落到第一个 server 块,可能把内部站点或无关站点的证书暴露给外部。建议显式设置 default_server 并返回固定错误页,避免信息泄漏与误判。