<?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 Field Notes</title><link>https://www.xbcatm.com/tags/sni/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/tags/sni/index.xml" rel="self" type="application/rss+xml"/><item><title>SNI lets one IP host many TLS sites</title><link>https://www.xbcatm.com/posts/nginx-ssl-sni-vhosts/</link><pubDate>Mon, 07 Feb 2028 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/nginx-ssl-sni-vhosts/</guid><description>The Server Name Indication extension sends the target hostname in the handshake, so one server can pick the right certificate.</description><content:encoded>&lt;p&gt;Server Name Indication (SNI) sends the hostname during the TLS handshake, so one IP address can serve many different certificates and sites. Before SNI, the math was brutal: one certificate per IP, one IP per site, and IPv4 addresses spent like water. Name-based virtual hosting worked for plaintext HTTP but broke for TLS, because the server had to choose a certificate before the encrypted request revealed which site the client wanted. SNI resolves the paradox by moving the hostname earlier — into the handshake itself, ahead of encryption.&lt;/p&gt;
&lt;p&gt;The practical result is the modern shared edge: CDNs, PaaS routers, and single-box multi-tenant servers all multiplex thousands of certificates onto few addresses. But the mechanism has sharp edges worth knowing — privacy leakage, legacy clients, and the default-certificate trap — because each bites exactly when a new tenant onboards or an old client complains.&lt;/p&gt;
&lt;h2 id="the-name-goes-first"&gt;The name goes first&lt;/h2&gt;
&lt;p&gt;During the handshake, before keys are exchanged, the client sends the hostname it wants, and the server selects a matching certificate for that vhost. Because the name arrives before encryption, nginx can route and pick the right cert in one connection. The extension lives in the ClientHello message, the very first TLS flight, so no round trip is added — selection happens inline with the handshake the client was already performing.&lt;/p&gt;
&lt;p&gt;This ordering also explains why SNI cannot be retrofitted server-side alone. The client must send the extension; a client that never learned SNI gets whatever certificate the server offers by default, and validates it against the name it wanted. Ancient embedded libraries, decade-old payment terminals, and industrial controllers still show up without SNI in real fleets — know your client population before assuming universal support.&lt;/p&gt;
&lt;h2 id="match-by-server_name-block"&gt;Match by server_name block&lt;/h2&gt;
&lt;p&gt;The hostname is routing metadata, not secret data: it travels in plaintext so a load balancer can see it. nginx matches it against server_name to choose the right server block and certificate path. Each server block carries its own certificate and key directives, and nginx selects among blocks sharing one listen socket by comparing the SNI value against server_name entries — exact names first, then leading wildcards, then trailing wildcards, then regex, mirroring HTTP Host matching.&lt;/p&gt;
&lt;p&gt;Keep the TLS server_name list and the HTTP Host expectations identical. A common misconfiguration serves the right certificate but the wrong application: SNI matched block A while the Host header routes to block B, producing signed-but-wrong responses. Mirror the names and test both layers.&lt;/p&gt;
&lt;h2 id="control-the-default-fallback"&gt;Control the default fallback&lt;/h2&gt;
&lt;p&gt;When no vhost matches, the first configured certificate is offered as the default. Order your listen 443 ssl blocks so the fallback is what you want to serve for unknown names. The default block answers three kinds of clients: scanners probing your IP, SNI-less legacy clients, and typosquatted or expired-DNS names pointing at you. Each deserves a deliberate answer — a neutral landing page, a connection close, or a 444-style drop — not whichever tenant happened to be configured first.&lt;/p&gt;
&lt;p&gt;Mark the intended default explicitly with default_server so reordering files never silently changes behavior. And monitor the fallback: a traffic rise there is early signal of DNS misconfiguration, expiry elsewhere, or active scanning.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;The classic failure is the wrong-certificate alert after adding a tenant: the new server block exists, the certificate files are valid, but clients see the old default. Causes rank by frequency: the block was added to a file nginx never loaded (missing include or failed reload), server_name has a typo the eye skips, or two blocks claim the same name and the first wins. Diagnose with nginx -T dumping the effective config — read what the server sees, not what the files suggest — and test with openssl s_client -servername to observe exactly which certificate each name receives.&lt;/p&gt;
&lt;p&gt;The second failure is the privacy leak nobody planned for: SNI hostnames travel in cleartext, visible to every middlebox and passive observer between client and server. For sensitive tenants this leaks browsing intent even under fully valid TLS. Where that matters, Encrypted Client Hello is the emerging answer, but deployment is partial — document the exposure for tenants whose threat model includes network observers rather than promising secrecy TLS was never designed to give at this layer.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Verify per-name certificates and the fallback:&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;Each name must return its own certificate; the unknown name must return the deliberate default.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Server_Name_Indication"&gt;TLS SNI overview&lt;/a&gt;, Explains SNI purpose and privacy tradeoffs, checked 2026-06-01.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/configuring_https_servers.html"&gt;nginx Server Name Indication&lt;/a&gt;, official guide to HTTPS server configuration, checked 2026-09-05.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>