<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SNI · XB 现场笔记</title><link>https://hk.xbcatm.com/zh/tags/sni/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://hk.xbcatm.com/zh/tags/sni/index.xml" rel="self" type="application/rss+xml"/><item><title>SNI 让一个 IP 承载多个 HTTPS 站点</title><link>https://hk.xbcatm.com/zh/posts/nginx-ssl-sni-vhosts/</link><pubDate>Mon, 07 Feb 2028 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/posts/nginx-ssl-sni-vhosts/</guid><description>Server Name Indication 扩展在握手时告诉服务器目标主机名，使一台服务器能选对证书。</description><content:encoded>&lt;p&gt;Server Name Indication（简称 SNI）在 TLS 握手中把目标主机名发给服务器，使同一个 IP 地址能够承载多张证书、多个站点。没有 SNI 时，分配不同地址是一种办法，却不是唯一办法：一张包含多个主体备用名称（SAN）的证书，或覆盖相应名字的通配符证书，也可以供多个站点共用。问题在于先后顺序：在不使用 early data 的完整证书型 HTTPS 握手中，服务器选证书发生在收到标识应用站点的加密 HTTP 请求之前，不能等读到 Host 再为这次握手换证书。&lt;/p&gt;
&lt;p&gt;本文关注 nginx 的配置边界：哪个 server 块提供证书，名字没有匹配时采取什么策略，以及诊断命令究竟证明了什么。选中证书、客户端验证身份、把请求送到应用，是三个需要分别检查的决定，不能把其中一项成功当作另外两项也成功。&lt;/p&gt;
&lt;h2 id="名字最先报出"&gt;名字最先报出&lt;/h2&gt;
&lt;p&gt;客户端在 ClientHello 中携带 SNI，nginx 据此选择 TLS 服务配置。这个扩展使用已有的握手报文，不需要额外发送一次请求来询问站点。不过，把全过程说成“发生在加密之前”并不准确：TLS 1.3 在 ServerHello 之后加密证书报文。这里重要的是名字在证书选择时已经可用，而不是证书或整个握手始终明文传输。&lt;/p&gt;
&lt;p&gt;客户端必须发送 SNI，服务器才能在这个阶段按名字选择站点。不带 SNI 时，nginx 从对应监听地址与端口的缺省服务配置开始处理；该配置可能提供证书，也可能拒绝握手。若收到证书，客户端仍需检查信任链以及自己预期的身份。收到一张证书不等于接受它，也不等于已经知道后续请求会进入哪个应用。&lt;/p&gt;
&lt;h2 id="用-server_name-匹配"&gt;用 server_name 匹配&lt;/h2&gt;
&lt;p&gt;nginx 在相关监听地址和端口的 server_name 条目中匹配 SNI：精确名优先，然后是最长的前置星号通配名、最长的后置星号通配名，最后是配置顺序中第一个匹配的正则表达式。证书和私钥配置可以继承，也可以由多个站点共用；并不存在每个名字必须单独持有一张证书的规则。&lt;/p&gt;
&lt;p&gt;把预期主机名、证书 SAN、server_name 规则和应用路由放在同一份清单里。尤其要区分 nginx 的通配符匹配与证书的通配符匹配：两者覆盖范围不同，配置规则匹配成功，并不能证明证书也覆盖这个名字。检查新增站点时应逐项对应，避免只看到 server_name 中有通配符，就推断客户端一定会接受证书。&lt;/p&gt;
&lt;p&gt;证书选对也不等于请求一定到对的应用。nginx 还可能在处理 HTTP 请求行和 Host 头时重新选择服务配置。SNI 指向甲站、Host 指向乙站，不会替换已经用于握手的证书；TLS 也不为响应的业务含义背书。应明确哪些跨名字路由是允许的，再分别检查握手选择和应用层路由，不能把证书有效描述成“响应签名正确”，更不能据此认定租户授权已经成立。&lt;/p&gt;
&lt;h2 id="把缺省回退控制好"&gt;把缺省回退控制好&lt;/h2&gt;
&lt;p&gt;对每个监听地址与端口，用 default_server 明确指定缺省块；没有显式指定时，该监听器的第一个 server 块才是缺省。无 SNI 与无法匹配的 SNI 都需要明确处理策略，不能仅凭文件排列就断言未知名字一定收到第一张证书。显式指定缺省块，也能避免单纯调整配置顺序改变这个选择。&lt;/p&gt;
&lt;p&gt;缺省块可以提供合适的证书，也可以通过 nginx 1.19.4 起提供的 ssl_reject_handshake 拒绝 TLS 握手。中性 HTTP 落地页或 return 444 则是后续 HTTP 阶段的行为；如果客户端因证书不匹配而停止握手，就不会继续发送请求去看到那一页。这两种处理层次不能互相代替。未知名字和完全不带 SNI 的情况也应分别检查，按事先选定的策略解释结果。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;新增主机名却收到意外证书时，先检查监听地址、配置引用、server_name 拼写和证书分配。nginx -T 成功只表示本次读取的配置通过检查并被打印出来，不能证明运行中的工作进程已经加载同一版本。下面经过筛选和截断的片段还可能遗漏相关指令或错误，不能当作完整配置。应结合未截断输出与重载结果判断，再定位名字匹配问题；不要把配置文件存在误当作变更已经生效。&lt;/p&gt;
&lt;p&gt;没有 Encrypted Client Hello（ECH）时，普通 ClientHello 中的 SNI 对网络观察者可见，单独使用 TLS 1.3 不会隐藏它。成功协商 ECH 可以保护内部 ClientHello，包括目标主机名，但不意味着所有流量特征都被隐藏。本文没有测量某个站点的 ECH 部署或隐私效果，也不依据证书有效与否推断这些能力已经启用。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;
&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-nginx-ssl-sni-vhosts.detail.zh.svg" alt="不使用 early data 的完整证书型 HTTPS 握手中，ClientHello 的 SNI 选择 TLS 配置及证书；稍后的加密 HTTP Host 参与请求路由，因此证书正确不证明应用选择正确。" width="560" height="1468"&gt;
&lt;figcaption&gt;TLS 名称先于 HTTP 到达。 静态机制示意；未建立 TLS 连接或运行文章命令。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;以下是未在此执行的诊断模板，不是已验证的部署示例。使用前须把文档示例地址和主机名替换为自己获准测试的环境中的目标：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nginx -T 2&amp;gt;&amp;amp;1 | grep -B2 -A4 &amp;#39;server_name\|ssl_certificate &amp;#39; | head -n 40
openssl s_client -connect 203.0.113.10:443 -servername shop.example.com &amp;lt;/dev/null 2&amp;gt;/dev/null | openssl x509 -noout -subject -issuer
openssl s_client -connect 203.0.113.10:443 -servername unknown.invalid &amp;lt;/dev/null 2&amp;gt;/dev/null | openssl x509 -noout -subject
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这些管道打印证书的 subject 或 issuer 字段，既不显示 SAN 覆盖范围，也不能据此认定主机名与信任链验证通过。命令还隐藏了诊断信息；s_client 通常会在验证出错后继续，除非另行配置。没有输出可能来自连接失败、握手失败或下游证书解析失败，不能单独证明服务器按策略拒绝了未知名字。&lt;/p&gt;
&lt;p&gt;应依据所安装 OpenSSL 版本的验证选项，另外核对 SAN 身份、信任链、有效期以及验证失败的处理。在自己的授权环境里，分别确认已知名字的证书选择、未知名字和无 SNI 的策略，以及最终应用路由。不同名字共用覆盖其身份的证书是合理结果；未知名字可以收到配置指定的缺省证书，也可以被拒绝。本文没有执行这些检查，更没有把模板输出当作已经通过的断言。&lt;/p&gt;
&lt;h2 id="延伸阅读"&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hk.xbcatm.com/zh/notes/certificate-chain/"&gt;证书链是一条应当能打印出来的路径&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hk.xbcatm.com/zh/notes/certificate-expiry-is-a-date-not-a-dashboard-color/"&gt;证书过期是一个日期，而不是仪表盘上的颜色&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;p&gt;以下官方资料于 2026-10-18 访问，用于核对上述表述，不代表运行验证。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/configuring_https_servers.html"&gt;nginx HTTPS 服务配置&lt;/a&gt;，共享 SAN 或通配符证书及证书配置继承，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/server_names.html"&gt;nginx 名字匹配与服务配置选择阶段&lt;/a&gt;，名称匹配优先级及 TLS、请求行、Host 的不同选择阶段，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/request_processing.html"&gt;nginx 请求处理与缺省配置&lt;/a&gt;，监听地址与端口范围内的显式缺省配置及后续 HTTP 请求处理，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_reject_handshake"&gt;nginx 握手拒绝指令&lt;/a&gt;，TLS 握手拒绝策略及 nginx 1.19.4 引入该指令的版本边界，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/switches.html"&gt;nginx 命令行参数&lt;/a&gt;，配置检查与打印操作和独立的重载操作之间的区别，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6066.html#section-3"&gt;RFC 6066 第 3 节：服务名称指示&lt;/a&gt;，ClientHello 中的服务名称指示及未识别名称的处理，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8446.html"&gt;RFC 8446：TLS 1.3 握手&lt;/a&gt;，握手消息加密及完整握手与早期数据的适用边界，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9849.html"&gt;RFC 9849：加密客户端问候&lt;/a&gt;，内部 ClientHello 名称保护及仍可能暴露的信息，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.5/man1/openssl-s_client/"&gt;OpenSSL s_client 诊断与验证&lt;/a&gt;，SNI 选择与验证错误处理选项，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.5/man1/openssl-x509/"&gt;OpenSSL x509 证书信息输出&lt;/a&gt;，subject、issuer、SAN 信息查看与身份及信任链验证的区别，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.5/man1/openssl-verification-options/"&gt;OpenSSL 验证选项&lt;/a&gt;，主机名或 IP 身份验证与信任策略控制，已核验 2026-10-18。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>