<?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://www.xbcatm.com/zh/tags/sni/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/sni/index.xml" rel="self" type="application/rss+xml"/><item><title>SNI 让一个 IP 承载多个 HTTPS 站点</title><link>https://www.xbcatm.com/zh/posts/nginx-ssl-sni-vhosts/</link><pubDate>Mon, 07 Feb 2028 00:00:00 +0000</pubDate><guid>https://www.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 把悖论解开了：主机名提前一步，进握手本身，跑在加密前面。&lt;/p&gt;
&lt;p&gt;现实成果就是今天的共享边缘：内容分发、平台路由、单机多租户，全靠它把几千张证书复用到几个地址上。但机制有三处利齿值得认识——隐私外泄、老客户端、缺省证书陷阱——每一处都在新租户上线或老客户端投诉时准时咬人。&lt;/p&gt;
&lt;h2 id="名字最先报出"&gt;名字最先报出&lt;/h2&gt;
&lt;p&gt;在交换密钥之前，客户端就在握手里发出想要的主机名，服务器据此选对虚拟主机和证书。因为名字先于加密到达，nginx 才能在一条连接里同时完成路由与选证书。扩展住在第一条握手报文里，不多花一次往返——选证书和本来就要做的握手在同一趟车里完成。&lt;/p&gt;
&lt;p&gt;这个顺序也解释了它为什么不能只靠服务端单方面升级。客户端必须发这个扩展；没学过的客户端拿到缺省证书，拿它去校验自己想要的名字。老旧嵌入库、十年前的支付终端、工业控制器，真实机群里至今还有不带它的——认定全员支持之前，先看清自己的客户端构成。&lt;/p&gt;
&lt;h2 id="用-server_name-匹配"&gt;用 server_name 匹配&lt;/h2&gt;
&lt;p&gt;主机名是路由元数据，而不是秘密：它以明文往返，负载均衡器或中间设备都能读到。nginx 拿它去和 server_name 匹配，决定走哪个 server 块、用哪张证书。每个站点块自带证书与私钥，共享一个监听套接字的各块按规则比对：精确名先行，再通配头尾，最后正则，和超文本主机头的匹配同一套路。&lt;/p&gt;
&lt;p&gt;证书选对还不等于请求一定到对的应用。客户端会先验证证书的 SAN 是否覆盖它发送的名字，握手成功后才发送加密的 HTTP Host；反向代理若在第二层使用了另一套名字，证书看似正确，响应仍可能来自错误租户。把每个租户的 SNI 名、证书 SAN、server_name 和上游路由放在同一份清单里，新增或续期时逐项核对。SNI 解决的是证书选择，不是主机名保密，也不是应用授权。&lt;/p&gt;
&lt;p&gt;传输层的站点表和应用层的主机预期必须一致，否则重定向和规范化会和证书打架。典型错配是证书对了、应用错了：握手命中甲块，主机头却进了乙块的逻辑，吐出签名正确、内容错误的响应。两边名字照镜子，两层都测，有分歧就当缺陷修，别当灵活用。&lt;/p&gt;
&lt;h2 id="把缺省回退控制好"&gt;把缺省回退控制好&lt;/h2&gt;
&lt;p&gt;当虚拟主机一个都没命中时，nginx 会抛出第一个配置的证书作为缺省。因此 listen 443 ssl 块的顺序决定未知名字回退到哪个站点，别人直接敲 IP 也会命中这块。缺省块要招待三种客人：扫地址的扫描器、不带扩展的老客户端、域名过期或打错字误指过来的名字。每种都值得一个刻意的回答——中性落地页、直接断开、静默丢弃——而不是谁先配置谁待客。&lt;/p&gt;
&lt;p&gt;用缺省标记把意图写死，挪文件顺序也改不了行为。还要监控缺省块在伺候谁：它的流量突然上涨，往往是别处域名配错、证书过期或有人在扫的早期信号。回退块既是安全网也是传感器，它的请求速率单独成图，别和租户的混在一起。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;经典故障是加租户后的证书告警：新站点块写了，证书文件也合法，客户端却看到旧缺省。按概率排序：块加进了主配置从没加载的文件（缺引用或重载失败）、站点名拼写错到肉眼跳过、两个块抢同一个名字先到先得。诊断看有效配置转储——读服务器眼里的世界，别读文件暗示的世界——再用带名字指示的握手工具逐个名字看实际下发的是哪张证书。&lt;/p&gt;
&lt;p&gt;第二种故障是没人计划的隐私外泄：主机名明文走全程，客户端与服务器之间的每个中间盒、每个被动观察者都看得见。对敏感租户，这等于在证书全合法的情况下泄露了浏览意图。Encrypted Client Hello 是正在路上的答案，但部署还零零散散——对威胁模型包含网络观察者的租户如实告知，而不是许诺这一层本来就给不了的保密。&lt;/p&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;每个名字必须拿到自己的证书，未知名字必须拿到刻意的缺省。&lt;/p&gt;
&lt;h2 id="延伸阅读"&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/notes/certificate-chain/"&gt;证书链是一条应当能打印出来的路径&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.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;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Server_Name_Indication"&gt;TLS SNI 概览&lt;/a&gt;，解释 SNI 用途与隐私权衡，已核验 2026-06-01。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/configuring_https_servers.html"&gt;nginx HTTPS 服务配置&lt;/a&gt;，HTTPS 服务配置官方指南，已核验 2026-09-05。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>