用 Nginx stream 解决 Xray REALITY 和 HTTPS 站点的 443 端口冲突
记录一次用 Nginx stream 模块在 TCP 层按 SNI 分流,把公网 443 同时留给 Xray REALITY 和普通 HTTPS 站点的配置过程。
最近在服务器上整理 Nginx 和 Xray 配置时,遇到了一个很典型的问题:公网 443 端口只能被一个进程监听,但 Nginx 的 HTTPS 站点和 Xray REALITY 都希望使用它。
一开始的结构很直接:
公网 443
├── Nginx HTTPS 站点
└── Xray REALITY
结果也很直接:端口冲突。
如果只是普通 WebSocket 或 HTTP 代理,可能会想到在 Nginx 里写一个 location,再用 proxy_pass 转发到 Xray。但 REALITY 不是这种工作方式,它需要 TCP 层的 TLS 握手信息,不能被 Nginx 当作普通 HTTP 请求处理。
换句话说,这里需要的不是 HTTP 反向代理,而是 TCP 透传和 SNI 分流。
最终结构
调整后的结构是让 Nginx 继续占用公网 443,但这个 443 不再由 http 模块里的 HTTPS 站点直接监听,而是交给 stream 模块接管。
公网 443
↓
Nginx stream
↓ 按 SNI 分流
├── REALITY serverName → 127.0.0.1:10000 → Xray
└── 普通网站流量 → 127.0.0.1:8443 → Nginx HTTPS 站点
也就是:
- Nginx
stream监听公网443 - Xray REALITY 改为监听
127.0.0.1:10000 - 原来的 HTTPS 站点改为监听
127.0.0.1:8443 - Nginx 根据 TLS ClientHello 里的 SNI 决定流量发给谁
这里的关键点是 ssl_preread。它不会终止 TLS,也不会解密流量,只是在 TCP 层读取 TLS 握手里的 server name,然后做转发决策。
修改 Xray 监听端口
原来 Xray 可能是直接监听 443:
{
"port": 443
}
现在要把它改成本机端口,例如:
{
"listen": "127.0.0.1",
"port": 10000
}
这样 Xray 不再直接占用公网 443,只接收从 Nginx stream 转发过来的 REALITY 流量。
启用 Nginx stream 模块
如果配置 stream {} 后执行 nginx -t 报错:
unknown directive "stream"
通常说明当前 Nginx 没有加载 stream 模块。在 Debian 或 Ubuntu 上,可以安装动态模块:
sudo apt install -y libnginx-mod-stream
安装后再检查 Nginx 配置即可。
增加 stream 分流配置
在 /etc/nginx/nginx.conf 中,和 http {} 同级增加 stream {} 配置:
stream {
map $ssl_preread_server_name $backend {
learn.microsoft.com xray_reality;
default nginx_https;
}
upstream xray_reality {
server 127.0.0.1:10000;
}
upstream nginx_https {
server 127.0.0.1:8443;
}
server {
listen 443;
proxy_pass $backend;
ssl_preread on;
}
}
这里的 learn.microsoft.com 只是示例,需要替换为你 Xray REALITY 客户端配置里的 serverName。
如果 REALITY 使用的 serverName 和网站域名相同,就无法仅靠 SNI 把两类流量分开。因为从 Nginx 看,它们的 SNI 是同一个值。要让这种方案成立,REALITY 的 serverName 必须能和普通网站流量区分开。
调整原 HTTPS 站点
原来的站点配置可能是:
server {
listen 443 ssl;
server_name example.com;
# ...
}
现在公网 443 已经交给 stream 使用,所以站点要改为只监听本机的 8443:
server {
listen 127.0.0.1:8443 ssl http2;
server_name example.com;
# ...
}
这样普通 HTTPS 请求仍然会进入 Nginx,只是路径变成了:
公网 443 → Nginx stream → 127.0.0.1:8443 → Nginx HTTPS 站点
对外访问时,用户看到的还是标准 HTTPS 443 端口。
客户端是否需要修改
如果客户端原来就是连接服务器的 443 端口,并且 REALITY 的 serverName 没有变化,客户端通常不需要修改。
服务端内部只是把 Xray 从“直接监听公网 443”改成了“监听本机 10000,由 Nginx stream 转发过来”。这个变化对客户端是透明的。
检查配置
修改完成后,先检查 Nginx 配置:
sudo nginx -t
确认无误后重载 Nginx,并重启 Xray:
sudo systemctl reload nginx
sudo systemctl restart xray
最后看一下端口监听情况:
ss -lntp | grep -E '443|8443|10000'
正常情况下应该能看到类似结果:
0.0.0.0:443 nginx
127.0.0.1:8443 nginx
127.0.0.1:10000 xray
这说明公网 443 已经由 Nginx stream 接管,普通 HTTPS 站点和 Xray REALITY 都退到了本机端口,由 Nginx 在入口处统一分流。
小结
这次调整的核心思路很简单:443 端口仍然只对外暴露一个入口,但入口从普通 HTTPS server block 改成了 Nginx stream。
Nginx 不解密流量,只读取 SNI:
- 命中 REALITY 的
serverName,转发到 Xray 的本机端口 - 其他 HTTPS 流量,转发到本机 8443 的网站服务
用这种方式,既避免了 Nginx 和 Xray 同时争抢 443,也保留了普通网站的 HTTPS 访问能力。