什么是 SNI?
SNI(Server Name Indication,服务名称指示) 是 TLS 协议的一个扩展,允许客户端在 TLS 握手开始时提前声明要访问的服务器域名。这一看似简单的机制,彻底解决了 HTTPS 早期的核心痛点——单 IP 多域名 问题。
为什么需要 SNI?
在 SNI 出现之前,TLS 握手存在一个困境:
- 客户端发送 ClientHello 消息时,还没有指定要访问的具体域名
- 服务器收到 ClientHello 后,无法确定客户端想要哪个虚拟主机
- 服务器只能返回一个"默认"证书,无法为每个域名提供正确证书
这意味着:
- 一个 IP 地址只能部署一个 HTTPS 网站
- 要托管多个 HTTPS 网站,必须为每个网站分配独立 IP
SNI 的出现改变了这一局面。客户端在 ClientHello 中直接携带 server_name 扩展,声明目标域名,服务器据此返回对应证书。
工作原理
TLS 握手中的 SNI
SNI 作为 TLS 扩展(Extension),在 ClientHello 消息中发送:
|
|
服务器收到后,匹配 server_name 值,返回对应的证书链。
实际验证
使用 OpenSSL 可以查看 SNI 在握手中的应用:
|
|
对比两种方式的握手结果,观察证书返回的差异。
SNI 的格式
SNI 在 TLS 扩展中采用特定编码:
| 字段 | 值 |
|---|---|
| Extension Type | 0x0000 (host_name) |
| Server Name Type | 0x00 (DNS hostname) |
| Server Name | 目标域名(如 www.example.com) |
域名以 ASCII 编码传输,这意味着任何观察网络流量的第三方都能轻易得知用户访问的网站。
SNI 与隐私问题
隐私泄露风险
SNI 是明文传输的。 这带来一个重要隐私问题:
- 网络观察者(如 ISP、WiFi 热点运营者、政府监控)可以轻易看到用户访问的域名
- 即使流量本身已加密,SNI 仍然以明文形式暴露在网络层
- 这对于追求极致隐私的用户来说是一个明显漏洞
这与 HTTPS 加密整个连接的目的形成了微妙矛盾——用户访问的域名本身被暴露。
解决方案:加密 SNI (ESNI/ECH)
针对 SNI 隐私问题,业界提出了两种方案:
1. ESNI (Encrypted SNI)
ESNI 将 SNI 加密后再传输,密钥从 DNS TXT 记录中获取:
- 服务器发布公钥到 DNS
- 客户端从 DNS 获取公钥,加密 SNI 后发送
- 只有目标服务器能解密 SNI
但 ESNI 存在一个问题:DNS 查询仍然是明文的,攻击者仍可通过 DNS 看到目标域名。
2. ECH (Encrypted Client Hello)
ECH 是 ESNI 的升级版,被纳入 TLS 1.3 规范(RFC 8446):
- SNI 被加密,且加密密钥由服务器通过 DNS 记录发布
- 客户端使用服务器公钥加密完整的 ClientHello(包括 SNI)
- 即使 DNS 查询被监听,也无法得知目标域名
ECH 正在逐步部署,是隐私保护的推荐方案。
OpenSSL 中的 SNI 使用
基本用法
|
|
脚本化检测
批量检测多个域名的 SNI 支持:
|
|
常见问题排查
问题:连接返回错误的证书
|
|
确保 -servername 参数与实际访问域名一致。
SNI 配置注意事项
服务器端
- 确保配置完整:Web 服务器(Nginx/Apache)需正确配置 SNI
- 注意顺序:默认证书应放在配置首位
- 兼容性考虑:旧版客户端可能不支持 SNI,需保留 fallback 方案
客户端
- 正确设置 SNI:程序化访问 HTTPS 时,确保发送正确的 server_name
- 不要禁用 SNI:除非有特殊需求,否则会影响大多数现代网站的访问
总结
SNI 是现代 HTTPS 不可或缺的基础设施,解决了单 IP 多域名问题,使 HTTPS 的大规模部署成为可能。然而,其明文传输特性带来了隐私风险。
随着 ECH(Encrypted Client Hello)的逐步推广,SNI 的隐私问题将得到根本性解决。但在当前阶段,部署 SNI 仍是 HTTPS 服务的必修课。