使用 nginx 作为 sniproxy
把多个 HTTPS 服务部署在同一个 IP 上时,需要把传入连接根据 SNI 转发到不同的服务上。 Nginx 可以在不解密 TLS 的情况下,进行 SNI 代理。
把多个 HTTPS 服务部署在同一个 IP 上时,需要把传入连接根据 SNI 转发到不同的服务上。 Nginx 可以在不解密 TLS 的情况下,进行 SNI 代理。
思路
由于我们不想在 nginx 里解密 TLS 流量,只是单纯进行 TCP 连接转发,所以这里需要用到的是 nginx_stream_proxy_module. 为了能够根据 SNI 转发到不同后端,需要 ngx_stream_ssl_preread_module 来解析 TLS 协议头里的 SNI 字段。
配置
模块安装和激活
发行版打包的 nginx 可能是分模块、动态加载,并且拆包的。以 debian 为例,在 debian 12 里需要安装 nginx-full
包,并且在 nginx.conf
开头加入
load_module /usr/lib/nginx/modules/ngx_stream_module.so;
才能用上四层转发功能。
转发配置
首先,这里所有的内容都是属于 stream 模块的配置,需要写在 stream {}
块里面。这里的关键配置包含两个部分:
- 在
server
块中指定代理监听的地址、端口,打开ssl_preread
功能,并把转发目标指定为一个变量$upstream
. - 通过
map
块,根据 stream 模块预读提供的$ssl_pread_server_name
变量, 设置$upstream
的值。
例如:
stream {
map $ssl_preread_server_name $upstream {
foo.example.com [::1]:2001;
bar.example.com unix:/run/bar/bar.socket;
default unix:/nonexist;
}
server {
listen 443;
listen [::]:443;
proxy_pass $upstream;
ssl_preread on;
proxy_connect_timeout 15s;
proxy_half_close on;
}
}
在这里,我们设置了两个转发的主机名,转发目标可以是 IP:port, 也可以是 unix domain socket. 并且在最后的 default 目标设置成了一个不存在的 unix domain socket, 让它能在收到无效的主机名时关闭连接。(网络上各种随机的扫描很多的)
其余的参数可以按需添加,如 proxy_half_close on
让这个代理对应用层看起来更加透明。
完整配置
发行版的 nginx 默认配置可能很复杂。因为我们只用 sni proxy 功能,里面大部分都是我们不需要的。因此可以单独写一份最小配置来启动 nginx. Stream 转发功能是很简单的,我们需要关注的参数基本上就只有以下 3 个:
worker_processes auto; # worker 进程数, auto 为根据 CPU 数自动配置
worker_cpu_affinity auto; # worker 的 CPU 亲和性,自动配置就可以了
events {
worker_connections 1024; # 每个 worker 的并发连接数,根据你的工作负载,和服务器性能选择一个合适的
}
合并在一起就是:
load_module /usr/lib/nginx/modules/ngx_stream_module.so;
worker_processes auto;
worker_cpu_affinity auto;
events {
worker_connections 1024;
}
stream {
...
}
最后修改于 2025-08-03