Nginx 配置:基于 SNI 的分流

出于某些考虑,我需要在后端来处理 TLS 握手——因此 Nginx 传统意义上的反向代理在此场景下是行不通的,必须在 TCP 层面进行流量转发。

Server Name Indication (SNI) is an extension to the Transport Layer Security (TLS) computer networking protocol by which a client indicates which hostname it is attempting to connect to at the start of the handshaking process.

Wikipedia

试想把 Nginx 作为服务器唯一的网关,所有基于 TCP/IP 协议的流量首先流入 Nginx,经过分流逻辑判断后转发到对应的内部服务端口上——

由此,部署在 443 端口的 Nginx 便可以作为服务器唯一的入口来统一管理和记录所有入站流量。

stream 在 443 端口进行分流

已知问题:会使后端 log 的源 IP 为 127.0.0.1,可以用 proxy_mode 解决?(待研究

在此处列出所有可接受的域名 ,未接受的域名可以有两种处理方式:

  1. 拒绝握手(推荐,仅 1.19.4 版本以上)
  2. 以 Plain HTTP 返回 400 Bad Request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
stream {
map $ssl_preread_server_name $backend_name {
myserver.com myserver;
api.myserver.com api;
cdn.myserver.com cdn;
default bad;
}

upstream myserver {
server 127.0.0.1:666;
}

upstream api {
server 127.0.0.1:777;
}

upstream cdn {
server 127.0.0.1:888;
}

upstream bad {
server 127.0.0.1:400;
}

server {
listen 443 reuseport;
listen [::]:443 reuseport;
proxy_pass $backend_name;
ssl_preread on;
}
}

http {
# 返回 400
# server {
# listen 400;
# return 400;
# }

# 拒绝握手
server {
listen 400 ssl;
ssl_reject_handshake on;
}
}

此处会预读取 TLS 握手请求中的 SNI,与预设逻辑进行匹配,并将 TCP 流量完整地转发给任意端口

TCP 转发由 Nginx 的 ngx_stream_module 实现

加载静态mod

如果你的版本不能使用ssl_preread on则需要先将静态 mod 加载进来,即在 Nginx 配置文件加上:

1
load_module /usr/lib/nginx/modules/ngx_stream_module.so;

其他配置参考

Nginx 正确安装姿势

Nginx 配置:业务部分

Nginx 配置:证书

Nginx 配置:基于 SNI 的分流

Nginx 配置:安全