<?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://hk.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://hk.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://hk.xbcatm.com/posts/nginx-ssl-sni-vhosts/</link><pubDate>Mon, 07 Feb 2028 00:00:00 +0000</pubDate><guid>https://hk.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 requested hostname in the TLS handshake, letting one IP address serve different certificates and sites. Without SNI, separate addresses were one solution, but a shared certificate covering several subject alternative names (SANs) or an appropriate wildcard could also serve multiple sites. The problem is ordering: in a full certificate-based HTTPS handshake without early data, certificate selection precedes the encrypted HTTP request that identifies the application.&lt;/p&gt;
&lt;p&gt;This article focuses on nginx configuration: which server block supplies the certificate, what unmatched names encounter, and what an inspection command actually proves. Certificate selection, client verification, and application routing are separate decisions.&lt;/p&gt;
&lt;h2 id="the-name-goes-first"&gt;The name goes first&lt;/h2&gt;
&lt;p&gt;The client includes SNI in ClientHello. nginx can use that name while choosing the TLS server configuration; the extension does not require a separate request round trip. Avoid describing everything as happening before encryption: TLS 1.3 encrypts the certificate message after ServerHello.&lt;/p&gt;
&lt;p&gt;The client must send SNI for name-based selection at this stage. Without it, nginx starts from the default server for the listening address and port. That server may offer a certificate or reject the handshake. If a certificate is offered, the client still needs to validate its chain and expected identity; receiving a certificate is not acceptance.&lt;/p&gt;
&lt;h2 id="match-by-server_name-block"&gt;Match by server_name block&lt;/h2&gt;
&lt;p&gt;nginx compares the SNI name against server_name entries for the relevant listener. Matching prioritizes exact names, then the longest leading wildcard, the longest trailing wildcard, and the first matching regular expression. Certificate settings may be inherited or shared; every name does not require its own certificate.&lt;/p&gt;
&lt;p&gt;Keep a record linking each intended name, certificate SAN, server_name rule, and application route. nginx wildcard matching and certificate wildcard matching have different scopes: a configuration match alone does not establish certificate coverage.&lt;/p&gt;
&lt;p&gt;A correct certificate does not guarantee the correct application. nginx can select a server configuration again while processing the HTTP request line and Host header. SNI for one site and Host for another do not replace the handshake&amp;rsquo;s certificate, and TLS does not certify the response&amp;rsquo;s business meaning. Define the intended cross-name routing policy and check both layers.&lt;/p&gt;
&lt;h2 id="control-the-default-fallback"&gt;Control the default fallback&lt;/h2&gt;
&lt;p&gt;For each listening address and port, use default_server to make the intended default explicit. Without that designation, the first server for that listener is the default. Missing or unmatched SNI therefore needs a deliberate policy, rather than an assumption that the first certificate always wins.&lt;/p&gt;
&lt;p&gt;The default may serve a suitable certificate or reject the TLS handshake with ssl_reject_handshake, available since nginx 1.19.4. An HTTP landing page or return 444 acts later, after TLS; it cannot repair a certificate mismatch that prevents the client from sending HTTP. Test unknown-name and no-SNI cases separately against the chosen policy.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;If a new hostname receives an unexpected certificate, inspect the listener, included configuration, server_name spelling, and certificate assignment. A successful nginx -T checks and prints configuration files; it does not prove the running workers loaded that version. The filtered excerpt below can omit relevant settings and errors. Compare the full output and reload result before blaming name matching.&lt;/p&gt;
&lt;p&gt;Without Encrypted Client Hello (ECH), ordinary ClientHello SNI is visible to network observers. TLS 1.3 alone does not hide it. Successfully negotiated ECH protects the inner ClientHello, including its target name, but does not conceal every traffic characteristic. No ECH deployment or privacy measurement is established here.&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.en.svg" alt="In a full certificate-based HTTPS handshake without early data, SNI in ClientHello selects TLS configuration and its certificate. The later encrypted HTTP Host participates in request routing, so a correct certificate does not prove the right application was selected." width="560" height="1659"&gt;
&lt;figcaption&gt;The TLS name arrives before HTTP. Static mechanism illustration; no TLS connection or article command was run.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;This is an unexecuted diagnostic template, not a verified deployment. Replace the documentation address and example names with targets in your own authorized environment before use:&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;The pipelines print certificate subject/issuer fields. They neither display SAN coverage nor establish hostname and chain validation. Diagnostics are suppressed, and s_client normally continues after verification errors unless configured otherwise; empty output alone does not prove intentional rejection.&lt;/p&gt;
&lt;p&gt;Separately check SAN identity, trust chain, validity dates, and verification failures using the installed OpenSSL version&amp;rsquo;s documented verification controls. Confirm known-name selection, unknown-name and no-SNI policy, and application routing in that authorized environment. Shared certificates are valid when they cover the intended identities; an unmatched name may receive the configured default or be rejected. These outcomes remain untested here.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;p&gt;Official documentation accessed 2026-10-18 for the claims above; this is source review, not runtime verification.&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 configuration&lt;/a&gt;, shared SAN/wildcard certificates and inherited certificate settings, checked 2026-10-18.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/server_names.html"&gt;nginx server names and selection stages&lt;/a&gt;, name-matching precedence and separate TLS/request-line/Host selection stages, checked 2026-10-18.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/request_processing.html"&gt;nginx request processing and defaults&lt;/a&gt;, listener-scoped explicit defaults and later HTTP request processing, checked 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 handshake rejection&lt;/a&gt;, TLS handshake rejection policy and its nginx 1.19.4 introduction, checked 2026-10-18.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/switches.html"&gt;nginx command-line parameters&lt;/a&gt;, configuration testing/dumping versus the separate reload operation, checked 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, section 3: Server Name Indication&lt;/a&gt;, ClientHello server-name indication and unrecognized-name handling, checked 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 handshake&lt;/a&gt;, encrypted handshake messages and full-handshake/early-data boundaries, checked 2026-10-18.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9849.html"&gt;RFC 9849: Encrypted Client Hello&lt;/a&gt;, inner ClientHello name protection and remaining information exposure, checked 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 diagnostics and verification&lt;/a&gt;, SNI selection and verification-error handling controls, checked 2026-10-18.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.5/man1/openssl-x509/"&gt;OpenSSL x509 certificate printing&lt;/a&gt;, subject/issuer/SAN inspection versus identity and chain verification, checked 2026-10-18.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.5/man1/openssl-verification-options/"&gt;OpenSSL verification options&lt;/a&gt;, hostname/IP identity verification and trust-policy controls, checked 2026-10-18.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>