443 分流 · TLS 层路由

SNI 分流
一个 443 跑多个 HTTPS 服务

公网 IPv4 地址稀缺,一个 IP 的 443 却要塞下博客、DoH API、后台……SNI 分流让它们各走各的后端,互不打架。

RFC 6066server_name 扩展
ssl_preread不解密就读域名
443→多后端按 SNI 分流
零证书分流层不需要证书
TL;DR · 核心结论

SNI(Server Name Indication)是 TLS 握手时客户端在 ClientHello 报文里携带的 server_name 扩展(见 RFC 6066),告诉服务器"我要访问哪个域名"。用 nginx 的 stream 模块 + ssl_preread on;,可以在不解密 TLS 的前提下,仅凭 ClientHello 里的域名把 443 端口流量转发到不同后端:博客主站走默认后端,dns.23-4.cn 的 DoH 服务走本机 1443 端口。分流层全程不碰证书,证书由各后端自己持有。

What Is SNI

SNI 是什么:ClientHello 里的"我要找谁"

没有 SNI 之前,一个 IP:443 只能对应一个证书、一个网站

问题背景

TLS 握手发生在 HTTP 请求之前,服务器在看到 Host 头之前就要选择用哪张证书。如果一个 IP 上只绑一个 443,就只能服务一个 HTTPS 站点——IPv4 地址根本不够用。

SNI 的解法

客户端在最开始的 ClientHello 里就把目标域名写进 server_name 扩展。服务器(或前置的分流层)据此选择对应证书与后端,于是一个 IP:443 可以同时服务多个域名。

与 DoH 的关系

DoH 服务本身也是 HTTPS。当 dns.23-4.cn/dns-query 与博客共用一个 IP 的 443 时,就是靠 SNI 告诉 nginx"这条流量属于 DoH,转给 1443 后端"。

Nginx Stream

nginx stream + ssl_preread 分流配置

关键:在 stream 块里读 $ssl_preread_server_name,按它 proxy_pass 到不同后端

stream {
    # 按 SNI 把流量映射到不同后端
    map $ssl_preread_server_name $sni_backend {
        default             127.0.0.1:8443;   # 默认:博客主站
        dns.23-4.cn         127.0.0.1:1443;   # DoH 服务
    }

    server {
        listen 443;
        ssl_preread on;          # 只预判 ClientHello,不解密
        proxy_pass $sni_backend;
        proxy_timeout 300s;
    }
}

说明:ssl_preread 来自 ngx_stream_ssl_preread_module(官方文档见 nginx.org),它解析 ClientHello 里的 server_name 赋给 $ssl_preread_server_name;proxy_pass 行为见 ngx_stream_proxy_module。注意 stream 与 http 是 nginx 两个独立上下文,stream 配置通常放在 nginx.conf 顶层。

客户端访问SNI (server_name)分流后到达的后端
https://23-4.cn 博客23-4.cn127.0.0.1:8443(博客)
https://dns.23-4.cn/dns-querydns.23-4.cn127.0.0.1:1443(DoH)
其他未知域名任意127.0.0.1:8443(默认兜底)
Certificates

证书管理:分流层不碰证书

这是 ssl_preread 方案最省心的一点

nginx stream 层

它只做"看一眼 ClientHello、转发 TCP 流",不终止 TLS,因此不需要任何证书,也不用管证书过期续签。

博客后端(8443)

自己持有 23-4.cn 证书(Let's Encrypt 等),在自己的 https server 块里 ssl_certificate 配置。

DoH 后端(1443)

自己持有 dns.23-4.cn 证书。因为客户端是按 SNI 找到它的,它出示对应域名证书即可通过校验。

Troubleshooting

排查:怎么确认分流真的生效

不猜,用工具看握手

1. 用 openssl 测 SNI

openssl s_client -connect 服务器IP:443 -servername dns.23-4.cn,看返回的证书 CN/SAN 是不是 dns 子域。若不是,说明 SNI 没路由到 DoH 后端。

2. 检查 nginx 配置

nginx -t 验证语法;确认 stream 块已被加载(部分发行版需 --with-stream 或动态模块)。重载 systemctl reload nginx。

3. 看后端日志

访问一次目标域名,看 1443 / 8443 后端的访问日志有没有收到记录。收到 = 分流正确;没收到 = map 规则或 proxy_pass 写错。

4. 注意客户端不发 SNI

极少数老客户端/调试工具不带 SNI,会落到 default 后端。排障时优先用现代浏览器或带 -servername 的 openssl。

FAQ

常见问题

SNI 到底是什么?+
SNI(Server Name Indication)是 TLS 握手时客户端在 ClientHello 报文里带上的 server_name 扩展(RFC 6066 定义),告诉服务器自己想访问哪个域名。服务器据此在同一个 IP:443 上选择对应证书与后端。
nginx ssl_preread 会解密我的 TLS 流量吗?+
不会。ssl_preread 只读取 ClientHello 里的 server_name 字段做路由,然后把整条 TCP 流原样转发给后端,TLS 加密仍由后端服务器与客户端端到端完成,分流层看不到明文。
443 分流后证书放在哪里?+
证书由各自后端服务自己持有:博客后端持有博客域名证书,DoH 后端持有 dns 子域证书。nginx stream 分流层不需要任何证书,因为它不解密 TLS。
怎么验证 SNI 分流是否生效?+
用 openssl s_client -connect 服务器IP:443 -servername 测试域名,看握手返回的证书是不是该域名的;或看 nginx error.log / 后端访问日志确认流量被转发到了预期后端。
References

参考来源