Nginx Stream + SSL Preread 自动分流方案 — 完整技术文档
版本:v2.0
最后更新:2026-10-06
维护者:steven_roc
持续更新:本文档随项目演进持续更新
通过 Nginx 的 stream 模块 + ssl_preread 功能,在 TCP 层(OSI 第 4 层)检测入站流量的协议类型,实现:
用户无论输入 http:// 还是 https://,都能自动获得 HTTPS 加密访问。
本方案支持多个子域名共享同一服务器 IP,通过不同端口区分服务:
| 域名 | 外网端口 | 后端服务 | 用途 |
|---|---|---|---|
| chanking.zhonjin.com | 40130 | 127.0.0.1:5004 | 主 Web 服务 |
| mqtt.zhonjin.com | 40715 | 127.0.0.1:8085 | MQTT 服务 |
| baolin.zhonjin.com | 40716 | 127.0.0.1:5000 | 宝林服务 A |
| baolin.zhonjin.com | 40719 | 127.0.0.1:5001 | 宝林服务 B |
| 原则 | 说明 |
|---|---|
| TCP 层分流 | stream 模块工作在 TCP 层,不解密 TLS,仅通过 ClientHello 预读判断协议 |
| SSL 终结集中化 | 所有 SSL 证书由 Nginx 统一管理,后端服务只需提供 HTTP |
| 端口隔离 | 每个外网端口独立 stream server + 独立 map 变量,互不干扰 |
| 域名隔离 | 不同子域名通过独立的 server_name 配置区分,支持各自的后端服务 |
| 配置模块化 | 主配置与子域名配置分离,便于独立维护 |
| 向后兼容 | 新增服务组不影响已有配置,所有模块松耦合 |
外网用户请求
│
┌─────────────┼─────────────┐
│ │ │
chanking:40130 mqtt:40715 baolin:40716/40719
│ │ │
└─────────────┼─────────────┘
│
┌─────────┴─────────┐
│ Stream 模块 │
│ ssl_preread on │
│ 协议检测(TCP层) │
└─────────┬─────────┘
│
┌────────────┼────────────┐
│ TLS │ │ 明文 HTTP
▼ │ ▼
┌────────────┐ │ ┌────────────┐
│ HTTPS 服务 │ │ │ HTTP 服务 │
│ (SSL 终结) │ │ │ (301 跳转) │
│ :5005 │ │ │ :5006 │
│ :5007 │ │ │ :5008 │
│ :5009 │ │ │ :5010 │
│ :5011 │ │ │ :5012 │
└──────┬─────┘ │ └──────┬─────┘
│ │ │
└───────────┼────────────┘
│
┌─────────┴─────────┐
│ 反向代理 │
│ proxy_pass │
└─────────┬─────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ :5004 │ │ :8085 │ │ :5000 │ ...
│ 内网服务 │ │ 内网服务 │ │ 内网服务 │
└──────────┘ └──────────┘ └──────────┘
HTTPS 请求流程:
https://mqtt.zhonjin.com:40715ssl_preread on 预读 ClientHello,$ssl_preread_protocol 值为 TLS$backend_target_40715 匹配 default → 转发到 127.0.0.1:5007proxy_pass 将请求转发到后端 http://127.0.0.1:8085HTTP 请求流程:
http://baolin.zhonjin.com:40716ssl_preread 预读失败(非 TLS),$ssl_preread_protocol 值为空 ""$backend_target_40716 匹配 "" → 转发到 127.0.0.1:5010301 Moved Permanently,Location: https://baolin.zhonjin.com:40716$request_uri/etc/nginx/
├── nginx.conf # 主配置(全局参数 + 40130 端口 + include 指令)
├── conf.d/
│ └── zhonjin_http.conf # 子域名 HTTP 服务配置(mqtt/baolin)
└── stream.d/
└── zhonjin_stream.conf # 子域名 Stream 分流配置(mqtt/baolin)
| 文件 | 职责 | include 位置 |
|---|---|---|
nginx.conf |
全局参数、服务组1(40130)、include 指令 | 主配置入口 |
zhonjin_http.conf |
子域名的 HTTPS server + HTTP 跳转 server | http 块内 |
zhonjin_stream.conf |
子域名的 stream map + stream server | stream 块内 |
nginx -t 可验证所有 include 文件| 操作系统 | 版本要求 | 备注 |
|---|---|---|
| Ubuntu | 22.04+ | 推荐,原生支持 Nginx 1.24+ |
| Ubuntu | 24.04 | 完全兼容,推荐用于新部署 |
| Debian | 12+ | 原生支持 |
| CentOS/RHEL | 9+ | 需使用 EPEL 源 |
| Raspberry Pi OS | Bookworm+ | 兼容 Ubuntu 24.04 方案 |
| 功能 | 最低版本 | 推荐版本 | 说明 |
|---|---|---|---|
| stream 模块 | 1.9.0+ | 1.26+ | TCP 层代理核心 |
| ssl_preread | 1.11.5+ | 1.26+ | TCP 层 TLS 预读 |
| ssl_preread_buffer_size | 1.23.4+ | 1.26+ | 可选,大 ClientHello 支持 |
| http2 指令 | 1.25.1+ | 1.26+ | 新版 http2 on 指令 |
| stream include | 1.26+ | 1.26+ | stream 块内 include 指令 |
| 资源 | 最低配置 | 推荐配置 | 说明 |
|---|---|---|---|
| CPU | 1 核 | 2+ 核 | stream 模块 CPU 消耗较低 |
| 内存 | 256MB | 512MB+ | SSL session cache 占用内存 |
| 磁盘 | 1GB 可用 | 5GB+ | 日志存储需要空间 |
| 网络 | 100Mbps | 1Gbps | 取决于业务流量 |
# Ubuntu/Debian
sudo apt update
sudo apt install -y nginx-full # nginx-full 包含 stream 模块
sudo apt install -y certbot python3-certbot-nginx # Let's Encrypt 证书管理
# 验证 stream 模块已加载
nginx -V 2>&1 | grep -o with-stream
# 预期输出: with-stream
# 动态模块方式(如使用动态加载)
ls /usr/lib/nginx/modules/ngx_stream_module.so
# 静态编译方式(nginx -V 检查)
nginx -V 2>&1 | grep -E "with-stream|with-http_ssl_module|with-http_v2_module"
| 工具 | 用途 | 安装命令 |
|---|---|---|
htop |
进程监控 | apt install htop |
tcpdump |
网络抓包调试 | apt install tcpdump |
openssl |
证书/SSL 调试 | apt install openssl |
curl |
HTTP 测试 | apt install curl |
logrotate |
日志轮转(系统自带) | 默认已安装 |
| 域名 | 外网端口 | HTTPS 内网端口 | HTTP 跳转端口 | 后端服务 | 用途 |
|---|---|---|---|---|---|
| chanking.zhonjin.com | 40130 | 5005 | 5006 | 127.0.0.1:5004 | 主 Web 服务 |
| mqtt.zhonjin.com | 40715 | 5007 | 5008 | 127.0.0.1:8085 | MQTT 服务 |
| baolin.zhonjin.com | 40716 | 5009 | 5010 | 127.0.0.1:5000 | 宝林服务 A |
| baolin.zhonjin.com | 40719 | 5011 | 5012 | 127.0.0.1:5001 | 宝林服务 B |
# UFW 防火墙
sudo ufw allow 40130/tcp
sudo ufw allow 40715/tcp
sudo ufw allow 40716/tcp
sudo ufw allow 40719/tcp
# 或使用 iptables
sudo iptables -A INPUT -p tcp --dport 40130 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 40715 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 40716 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 40719 -j ACCEPT
注意:内部端口(5005-5012)无需对外开放,stream 模块通过 localhost 转发即可。
主配置文件包含:
load_module 加载 stream 模块include 指令引入子域名配置
# nginx.conf 末尾 include 指令
http {
# ... 服务组 1 配置 ...
include /etc/nginx/conf.d/zhonjin_http.conf;
}
stream {
# ... 服务组 1 的 stream 配置 ...
include /etc/nginx/stream.d/zhonjin_stream.conf;
}
stream {
# 独立 map 变量(每个端口唯一)
map $ssl_preread_protocol $backend_target_40715 {
"" 127.0.0.1:5008; # 明文HTTP → 301跳转
default 127.0.0.1:5007; # TLS流量 → SSL入口
}
server {
listen 40715 backlog=4096;
tcp_nodelay on;
ssl_preread on;
proxy_pass $backend_target_40715;
proxy_connect_timeout 10s;
proxy_timeout 3600s;
proxy_next_upstream off;
access_log /var/log/nginx/stream_access_40715.log stream_main;
}
}
关键参数说明:
| 参数 | 值 | 说明 |
|---|---|---|
backlog |
4096 | TCP 监听队列长度,高并发时增大 |
tcp_nodelay |
on | 禁用 Nagle 算法,减少小包延迟 |
ssl_preread |
on | 只读预读 ClientHello,不解密 |
proxy_connect_timeout |
10s | 与后端建立连接的超时 |
proxy_timeout |
3600s | 连接空闲超时,WebSocket 长连接需设大 |
proxy_next_upstream |
off | 禁用重试,防止非幂等请求重复执行 |
server {
listen 127.0.0.1:5007 ssl;
http2 on;
server_name mqtt.zhonjin.com;
ssl_certificate /etc/letsencrypt/live/zhonjin.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zhonjin.com/privkey.pem;
ssl_session_cache shared:SSL_mqtt:5m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:...;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://127.0.0.1:8085;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 5s;
proxy_read_timeout 3600s;
proxy_send_timeout 60s;
}
}
server {
listen 127.0.0.1:5008;
server_name mqtt.zhonjin.com;
return 301 https://$host:40715$request_uri;
}
注意:301 跳转 URL 中的端口号必须与对应的外网端口一致。$host 变量会自动保留用户访问的域名。
所有子域名共用同一个通配符证书:
证书覆盖域名:
- zhonjin.com
- *.zhonjin.com(覆盖所有子域名)
├── chanking.zhonjin.com
├── mqtt.zhonjin.com
└── baolin.zhonjin.com
# 申请通配符证书(DNS 验证)
sudo certbot certonly --manual --preferred-challenges dns \
-d zhonjin.com -d "*.zhonjin.com"
按提示添加 DNS TXT 记录完成验证。
| 文件 | 路径 | 说明 |
|---|---|---|
| 证书链 | /etc/letsencrypt/live/zhonjin.com/fullchain.pem |
包含中间证书 |
| 私钥 | /etc/letsencrypt/live/zhonjin.com/privkey.pem |
证书私钥 |
| 根证书 | /etc/letsencrypt/live/zhonjin.com/chain.pem |
仅中间 CA |
# 测试续期
sudo certbot renew --dry-run
# 添加 crontab 自动续期
sudo crontab -e
# 添加以下行(每天凌晨 3 点检查)
0 3 * * * certbot renew --quiet --post-hook "nginx -s reload"
# 检查证书有效期
openssl x509 -in /etc/letsencrypt/live/zhonjin.com/fullchain.pem -noout -dates
# 检查证书域名覆盖
openssl x509 -in /etc/letsencrypt/live/zhonjin.com/fullchain.pem -noout -text | grep -A1 "Subject Alternative Name"
| 配置项 | 值 | 说明 |
|---|---|---|
ssl_protocols |
TLSv1.2 TLSv1.3 | 禁用 SSLv3/TLSv1.0/1.1 |
ssl_session_tickets |
off | 禁用 session tickets,增强前向安全 |
ssl_prefer_server_ciphers |
off | 现代推荐做法,优先客户端偏好 |
| HSTS | max-age=31536000 | 强制浏览器使用 HTTPS,1 年有效期 |
127.0.0.1,外部无法直接访问# 可选:限制请求体大小(在 http 块中)
client_max_body_size 100m;
# 可选:添加安全响应头
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
| 日志文件 | 类型 | 说明 |
|---|---|---|
/var/log/nginx/error.log |
error | 全局错误日志 |
/var/log/nginx/access.log |
access | HTTP 服务访问日志 |
/var/log/nginx/stream_error.log |
error | Stream 模块错误日志 |
/var/log/nginx/stream_access_40130.log |
access | Stream 端口 40130 访问日志 |
/var/log/nginx/stream_access_40715.log |
access | Stream 端口 40715 访问日志 |
/var/log/nginx/stream_access_40716.log |
access | Stream 端口 40716 访问日志 |
/var/log/nginx/stream_access_40719.log |
access | Stream 端口 40719 访问日志 |
# /etc/logrotate.d/nginx(系统通常已自带)
/var/log/nginx/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}
# 查看最近 100 条 stream 访问记录
tail -100 /var/log/nginx/stream_access_40715.log
# 统计各端口流量
wc -l /var/log/nginx/stream_access_*.log
# 查看错误日志
tail -f /var/log/nginx/stream_error.log
# /etc/sysctl.conf 添加
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_keepalive_time = 600
sudo sysctl -p
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 8192;
multi_accept on;
use epoll;
}
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 114.114.114.114 valid=300s;
resolver_timeout 5s;
# 1. 安装 Nginx(含 stream 模块)
sudo apt install -y nginx-full certbot python3-certbot-nginx
# 2. 申请通配符 SSL 证书
sudo certbot certonly --manual --preferred-challenges dns \
-d zhonjin.com -d "*.zhonjin.com"
# 3. 创建配置目录
sudo mkdir -p /etc/nginx/conf.d /etc/nginx/stream.d
# 4. 部署配置文件
sudo cp nginx.conf /etc/nginx/nginx.conf
sudo cp zhonjin_http.conf /etc/nginx/conf.d/zhonjin_http.conf
sudo cp zhonjin_stream.conf /etc/nginx/stream.d/zhonjin_stream.conf
# 5. 测试配置
sudo nginx -t
# 6. 重载 Nginx
sudo systemctl reload nginx
# 7. 配置防火墙
sudo ufw allow 40130/tcp
sudo ufw allow 40715/tcp
sudo ufw allow 40716/tcp
sudo ufw allow 40719/tcp
# 8. 验证服务
curl -I http://chanking.zhonjin.com:40130 # 应返回 301
curl -I https://chanking.zhonjin.com:40130 # 应返回 200
curl -I http://mqtt.zhonjin.com:40715 # 应返回 301
curl -I https://mqtt.zhonjin.com:40715 # 应返回 200
curl -I http://baolin.zhonjin.com:40716 # 应返回 301
curl -I https://baolin.zhonjin.com:40716 # 应返回 200
curl -I http://baolin.zhonjin.com:40719 # 应返回 301
curl -I https://baolin.zhonjin.com:40719 # 应返回 200
# 查看 Nginx 状态
sudo systemctl status nginx
# 重载配置(不中断服务)
sudo nginx -t && sudo systemctl reload nginx
# 查看当前监听端口
sudo ss -tlnp | grep nginx
# 查看连接数
sudo ss -tnp | grep nginx | wc -l
# 备份当前配置
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%Y%m%d)
sudo cp /etc/nginx/conf.d/zhonjin_http.conf /etc/nginx/conf.d/zhonjin_http.conf.bak.$(date +%Y%m%d)
sudo cp /etc/nginx/stream.d/zhonjin_stream.conf /etc/nginx/stream.d/zhonjin_stream.conf.bak.$(date +%Y%m%d)
| 问题 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
nginx -t 报错 unknown directive |
stream 模块未加载 | nginx -V 2>&1 \| grep stream |
安装 nginx-full 或加载模块 |
nginx -t 报错 include 文件找不到 |
include 路径错误 | 检查文件是否存在 | 确认路径和目录权限 |
| 端口无响应 | 防火墙阻挡 | sudo ufw status |
放行端口 |
| HTTP 不跳转 | map 变量名冲突 | 检查 stream 中 map 变量是否唯一 | 每个端口使用独立变量名 |
| SSL 握手失败 | 证书路径错误 | openssl s_client -connect localhost:5007 |
检查证书路径和权限 |
| 域名解析错误 | DNS 未配置 | dig mqtt.zhonjin.com |
添加 A 记录指向服务器 IP |
| WebSocket 断开 | proxy_timeout 过短 | 检查 stream 和 http 超时配置 | 保持两者一致(3600s) |
| 502 Bad Gateway | 后端服务未启动 | curl http://127.0.0.1:8085 |
启动后端服务 |
| 端口冲突 | 其他程序占用 | sudo ss -tlnp \| grep :PORT |
停止冲突程序或更换端口 |
# 1. TCP 层面验证 stream 转发
openssl s_client -connect mqtt.zhonjin.com:40715 -servername mqtt.zhonjin.com
# 2. 检查 SSL 证书链
echo | openssl s_client -connect mqtt.zhonjin.com:40715 -showcerts 2>/dev/null | openssl x509 -noout -dates
# 3. 查看 stream 模块流量
tail -f /var/log/nginx/stream_access_40715.log
# 4. 抓包分析 TCP 流量
sudo tcpdump -i any port 40715 -n -c 100
# 5. 验证 HTTP 跳转
curl -v http://mqtt.zhonjin.com:40715 2>&1 | grep Location
当需要新增一组端口映射时(以 newservice.zhonjin.com:40800 → 后端 6000 为例):
Step 1:在 zhonjin_http.conf 中添加 server 配置
# HTTPS 服务
server {
listen 127.0.0.1:NEW_HTTPS_PORT ssl;
http2 on;
server_name newservice.zhonjin.com;
# ... SSL 配置(复制已有模式,注意 ssl_session_cache 名称唯一)
location / {
proxy_pass http://127.0.0.1:6000;
# ... proxy 配置
}
}
# HTTP 跳转
server {
listen 127.0.0.1:NEW_HTTP_PORT;
server_name newservice.zhonjin.com;
return 301 https://$host:40800$request_uri;
}
Step 2:在 zhonjin_stream.conf 中添加分流配置
map $ssl_preread_protocol $backend_target_40800 {
"" 127.0.0.1:NEW_HTTP_PORT;
default 127.0.0.1:NEW_HTTPS_PORT;
}
server {
listen 40800 backlog=4096;
tcp_nodelay on;
ssl_preread on;
proxy_pass $backend_target_40800;
proxy_connect_timeout 10s;
proxy_timeout 3600s;
proxy_next_upstream off;
access_log /var/log/nginx/stream_access_40800.log stream_main;
}
Step 3:更新防火墙并重载
sudo ufw allow 40800/tcp
sudo nginx -t && sudo systemctl reload nginx
| 端口范围 | 用途 | 示例 |
|---|---|---|
| 40100-40199 | 核心 Web 服务 | 40130 |
| 40700-40799 | 扩展服务组 | 40715, 40716, 40719 |
| 40800-40899 | 预留扩展 | - |
| 文件 | 路径 | 说明 |
|---|---|---|
| nginx.conf | 项目根目录 | 主配置 |
| zhonjin_http.conf | 项目根目录 | HTTP 服务配置 |
| zhonjin_stream.conf | 项目根目录 | Stream 分流配置 |
| zhonjin.com.conf | 项目根目录 | 配置总览说明 |