同一个 443 端口跑多个 TLS 服务:Nginx stream + SNI 分流实战
一、问题:一个公网 IP,443 端口只能给一个服务
云主机通常只有一个公网 IPv4,但业务上经常需要让 443 端口同时服务多个 TLS 后端:主站、内部管理面板、以及一个需要长连接的私有隧道服务。用七层反向代理做不到这件事——七层必须先在 Nginx 里完成 TLS 握手,再按 Host 或路径转发,后端拿到的就是明文 HTTP,原来的 TLS 服务根本没法工作。
正确的位置是把分流下沉到四层:Nginx 用 ssl_preread 只看 ClientHello 里的 SNI 字段,按域名把原始 TCP 流量丢给不同后端,TLS 握手完全交给后面的服务自己完成。Nginx 全程不碰证书,也不需要为每个域名准备证书。
二、结构
客户端 → 公网 443 → nginx stream(ssl_preread 读 SNI)→ 命中域名转发到对应 upstream;没命中(IP 直连、未知域名)走 default 分支。整个链路只有一次 TCP 连接,Nginx 不终止 TLS。
三、核心配置
stream {
map $ssl_preread_server_name $backend {
www.example.com web;
panel.example.com panel;
default fallback;
}
upstream web { server 127.0.0.1:8443; }
upstream panel { server 127.0.0.1:9443; }
upstream fallback { server 127.0.0.1:8443; }
server {
listen 443;
listen [::]:443;
ssl_preread on;
proxy_pass $backend;
proxy_protocol on;
proxy_connect_timeout 5s;
proxy_timeout 600s;
}
}
ssl_preread 必须开在 server 块里,map 才能读到 $ssl_preread_server_name。proxy_timeout 建议调大,长连接业务默认 10 分钟就断会很难受。
四、三个实际踩过的坑
1) 出现空 SNI 全部落到 default。客户端用 IP 直连、或者先发明文 HTTP 探测时,$ssl_preread_server_name 是空字符串,会走 default。所以 default 分支要能兜住这些流量,否则表现为"连接被重置",排查时容易误判成防火墙问题。
2) 打开 proxy_protocol 后日志全是 127.0.0.1。后端站点必须补上 set_real_ip_from 127.0.0.1; real_ip_header proxy_protocol; 两行,否则访问日志、限流、风控全部按本机地址计算,等于失效。
3) 后端监听地址写成了 0.0.0.0。这样等于留了一条绕过分流的旁路,任何人直连该端口都能跳过 SNI 判断。内部上游统一监听 127.0.0.1,再用安全组把高位端口全部关掉,才算是真的收口。
五、验证
用 openssl s_client -connect IP:443 -servername www.example.com 看返回的证书链,再用另一个 -servername 对比,两次拿到的证书应该不同;如果相同,说明 map 没生效或者后端监听串了。最后 curl -I https://域名 确认业务正常,nginx -t && systemctl reload nginx 确认配置可回滚。
六、小结
四层分流的本质是"不碰 TLS":代理和站点各自完成握手,互不干扰,新增后端只是加一个 map 条目加一个 upstream。代价是 Nginx 看不到 HTTP 层信息,所有按路径、按 Header 的策略都要下沉到后端。想清楚这条边界,配置其实只有二十来行。
- 点赞
- 收藏
- 关注作者
评论(0)