Server Name Indication(简称 SNI)在 TLS 握手中把目标主机名发给服务器,使同一个 IP 地址能够承载多张证书、多个站点。没有 SNI 时,分配不同地址是一种办法,却不是唯一办法:一张包含多个主体备用名称(SAN)的证书,或覆盖相应名字的通配符证书,也可以供多个站点共用。问题在于先后顺序:在不使用 early data 的完整证书型 HTTPS 握手中,服务器选证书发生在收到标识应用站点的加密 HTTP 请求之前,不能等读到 Host 再为这次握手换证书。

本文关注 nginx 的配置边界:哪个 server 块提供证书,名字没有匹配时采取什么策略,以及诊断命令究竟证明了什么。选中证书、客户端验证身份、把请求送到应用,是三个需要分别检查的决定,不能把其中一项成功当作另外两项也成功。

名字最先报出

客户端在 ClientHello 中携带 SNI,nginx 据此选择 TLS 服务配置。这个扩展使用已有的握手报文,不需要额外发送一次请求来询问站点。不过,把全过程说成“发生在加密之前”并不准确:TLS 1.3 在 ServerHello 之后加密证书报文。这里重要的是名字在证书选择时已经可用,而不是证书或整个握手始终明文传输。

客户端必须发送 SNI,服务器才能在这个阶段按名字选择站点。不带 SNI 时,nginx 从对应监听地址与端口的缺省服务配置开始处理;该配置可能提供证书,也可能拒绝握手。若收到证书,客户端仍需检查信任链以及自己预期的身份。收到一张证书不等于接受它,也不等于已经知道后续请求会进入哪个应用。

用 server_name 匹配

nginx 在相关监听地址和端口的 server_name 条目中匹配 SNI:精确名优先,然后是最长的前置星号通配名、最长的后置星号通配名,最后是配置顺序中第一个匹配的正则表达式。证书和私钥配置可以继承,也可以由多个站点共用;并不存在每个名字必须单独持有一张证书的规则。

把预期主机名、证书 SAN、server_name 规则和应用路由放在同一份清单里。尤其要区分 nginx 的通配符匹配与证书的通配符匹配:两者覆盖范围不同,配置规则匹配成功,并不能证明证书也覆盖这个名字。检查新增站点时应逐项对应,避免只看到 server_name 中有通配符,就推断客户端一定会接受证书。

证书选对也不等于请求一定到对的应用。nginx 还可能在处理 HTTP 请求行和 Host 头时重新选择服务配置。SNI 指向甲站、Host 指向乙站,不会替换已经用于握手的证书;TLS 也不为响应的业务含义背书。应明确哪些跨名字路由是允许的,再分别检查握手选择和应用层路由,不能把证书有效描述成“响应签名正确”,更不能据此认定租户授权已经成立。

把缺省回退控制好

对每个监听地址与端口,用 default_server 明确指定缺省块;没有显式指定时,该监听器的第一个 server 块才是缺省。无 SNI 与无法匹配的 SNI 都需要明确处理策略,不能仅凭文件排列就断言未知名字一定收到第一张证书。显式指定缺省块,也能避免单纯调整配置顺序改变这个选择。

缺省块可以提供合适的证书,也可以通过 nginx 1.19.4 起提供的 ssl_reject_handshake 拒绝 TLS 握手。中性 HTTP 落地页或 return 444 则是后续 HTTP 阶段的行为;如果客户端因证书不匹配而停止握手,就不会继续发送请求去看到那一页。这两种处理层次不能互相代替。未知名字和完全不带 SNI 的情况也应分别检查,按事先选定的策略解释结果。

坏掉的样子

新增主机名却收到意外证书时,先检查监听地址、配置引用、server_name 拼写和证书分配。nginx -T 成功只表示本次读取的配置通过检查并被打印出来,不能证明运行中的工作进程已经加载同一版本。下面经过筛选和截断的片段还可能遗漏相关指令或错误,不能当作完整配置。应结合未截断输出与重载结果判断,再定位名字匹配问题;不要把配置文件存在误当作变更已经生效。

没有 Encrypted Client Hello(ECH)时,普通 ClientHello 中的 SNI 对网络观察者可见,单独使用 TLS 1.3 不会隐藏它。成功协商 ECH 可以保护内部 ClientHello,包括目标主机名,但不意味着所有流量特征都被隐藏。本文没有测量某个站点的 ECH 部署或隐私效果,也不依据证书有效与否推断这些能力已经启用。

不使用 early data 的完整证书型 HTTPS 握手中,ClientHello 的 SNI 选择 TLS 配置及证书;稍后的加密 HTTP Host 参与请求路由,因此证书正确不证明应用选择正确。
TLS 名称先于 HTTP 到达。 静态机制示意;未建立 TLS 连接或运行文章命令。

快速验证

以下是未在此执行的诊断模板,不是已验证的部署示例。使用前须把文档示例地址和主机名替换为自己获准测试的环境中的目标:

nginx -T 2>&1 | grep -B2 -A4 'server_name\|ssl_certificate ' | head -n 40
openssl s_client -connect 203.0.113.10:443 -servername shop.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer
openssl s_client -connect 203.0.113.10:443 -servername unknown.invalid </dev/null 2>/dev/null | openssl x509 -noout -subject

这些管道打印证书的 subject 或 issuer 字段,既不显示 SAN 覆盖范围,也不能据此认定主机名与信任链验证通过。命令还隐藏了诊断信息;s_client 通常会在验证出错后继续,除非另行配置。没有输出可能来自连接失败、握手失败或下游证书解析失败,不能单独证明服务器按策略拒绝了未知名字。

应依据所安装 OpenSSL 版本的验证选项,另外核对 SAN 身份、信任链、有效期以及验证失败的处理。在自己的授权环境里,分别确认已知名字的证书选择、未知名字和无 SNI 的策略,以及最终应用路由。不同名字共用覆盖其身份的证书是合理结果;未知名字可以收到配置指定的缺省证书,也可以被拒绝。本文没有执行这些检查,更没有把模板输出当作已经通过的断言。

延伸阅读

来源

以下官方资料于 2026-10-18 访问,用于核对上述表述,不代表运行验证。