# 问答归档 (Q&A Archive) Nginx Stream + SSL Preread 自动分流方案 — 技术问答归档 > **持续更新**:每次技术问答后自动归档 > **最后更新**:2026-10-06 --- ## 目录 - [架构相关](#架构相关) - [配置相关](#配置相关) - [域名与证书](#域名与证书) - [性能相关](#性能相关) - [故障排查](#故障排查) - [端口扩展](#端口扩展) - [树莓派部署](#树莓派部署) --- ## 架构相关 ### Q1: 为什么使用 stream + ssl_preread 而不是传统的 http 模块 redirect? **A:** 传统方案需要在 Nginx 的 HTTP 模块中直接监听外网端口并处理 SSL,所有 HTTP 和 HTTPS 流量必须使用同一端口。而 stream + ssl_preread 方案的优势: 1. **端口灵活性**:可以在任意非标准端口同时处理 HTTP 和 HTTPS 流量 2. **TCP 层分流**:不解密 TLS,性能开销更低 3. **协议检测**:通过 `ssl_preread` 在 TCP 层检测 ClientHello,无需证书即可完成分流 4. **后端隔离**:后端服务只需提供 HTTP,SSL 终结完全由 Nginx 处理 ### Q2: 为什么不直接用 Nginx 的 `if ($scheme = http)` 来做跳转? **A:** `if` 指令在 Nginx 中是 "if is evil" 的,有以下问题: 1. 在 `stream` 上下文中无法使用 `$scheme` 变量(stream 不解析 HTTP) 2. `if` 在 `location` 上下文中有已知的副作用和限制 3. 通过 stream 层的物理分流更干净,每个路径独立互不干扰 ### Q3: 每个端口都需要独立的 map 变量吗? **A:** 是的。Nginx 的 map 指令在同一上下文中,如果多个 stream server 共享同一个 map,它们会路由到相同的后端。每个端口需要独立的服务组(不同的 SSL 终结端口和不同的后端),因此需要独立的 map 变量。 ### Q4: 为什么把子域名配置拆成独立文件而不是全部写在 nginx.conf 里? **A:** 模块化配置的优势: 1. **独立维护**:新增子域名只需编辑对应的 include 文件,不影响主配置 2. **减少冲突**:不同团队/项目的配置不会互相干扰 3. **易于审计**:配置分散在小文件中,便于审查 4. **热重载安全**:`nginx -t` 可验证所有 include 文件 5. **结构清晰**:`nginx.conf` 保持简洁,只负责全局配置和 include 指令 --- ## 配置相关 ### Q5: `backlog=4096` 的含义是什么?需要调整吗? **A:** `backlog` 是 TCP 监听队列的最大长度。超过 backlog 的连接会被丢弃。 - 默认值通常是 511(Linux) - 4096 适合中等并发场景 - 高并发场景可增加到 65535(需同时调整 `net.core.somaxconn`) ### Q6: `proxy_timeout 3600s` 为什么要设这么大? **A:** 需要与 HTTP 模块的 `proxy_read_timeout` 保持一致: 1. stream 层的超时控制的是 TCP 连接空闲超时 2. 如果后端有 WebSocket 长连接,可能需要保持数小时 3. 设为 3600s 可覆盖绝大多数长连接场景 4. 如果确认没有长连接需求,可以缩短到 60-300s 以释放资源 ### Q7: `proxy_next_upstream off` 为什么禁用故障转移? **A:** 在 stream 上下文中: 1. TCP 层无法确定请求是否已部分传输 2. 非幂等请求(如 POST)重试可能导致数据重复 3. 只有一个 upstream 时此选项实际无影响,但显式禁用更安全 ### Q8: `include` 指令的路径有什么要求? **A:** - `include` 路径可以是绝对路径或相对路径(相对于 nginx.conf 所在目录) - 推荐使用绝对路径,避免歧义 - 支持通配符:`include /etc/nginx/conf.d/*.conf;` - include 的文件中不能再嵌套 include stream 块(stream 块不支持嵌套 include stream) --- ## 域名与证书 ### Q9: 多个子域名可以共用同一个 SSL 证书吗? **A:** 可以,使用通配符证书 `*.zhonjin.com` 即可覆盖所有子域名: - `chanking.zhonjin.com` ✓ - `mqtt.zhonjin.com` ✓ - `baolin.zhonjin.com` ✓ - 其他 `*.zhonjin.com` ✓ 申请命令: ```bash sudo certbot certonly --manual --preferred-challenges dns \ -d zhonjin.com -d "*.zhonjin.com" ``` ### Q10: 不同子域名使用同一个 IP,Nginx 如何区分? **A:** 本方案中,不同子域名通过**不同端口**区分,而非 SNI 区分: - `chanking.zhonjin.com:40130` → stream 端口 40130 - `mqtt.zhonjin.com:40715` → stream 端口 40715 - `baolin.zhonjin.com:40716` → stream 端口 40716 每个 stream server 独立监听自己的端口,转发到对应的 HTTPS server,HTTPS server 通过 `server_name` 匹配域名。 ### Q11: `ssl_preread` 能否根据 SNI(域名)分流到不同后端? **A:** 可以。`ssl_preread` 不仅能检测是否为 TLS,还能读取 SNI 信息: ```nginx map $ssl_preread_server_name $sni_backend { "mqtt.zhonjin.com" 127.0.0.1:8085; "baolin.zhonjin.com" 127.0.0.1:5000; default 127.0.0.1:5004; } ``` 但本方案使用端口区分,因此不需要 SNI 分流。 ### Q12: 通配符证书 `*.zhonjin.com` 是否覆盖 `zhonjin.com`(不带子域名前缀)? **A:** 不覆盖。通配符证书只匹配 `*.zhonjin.com`,不匹配裸域 `zhonjin.com`。申请时需要同时指定: ```bash -d zhonjin.com -d "*.zhonjin.com" ``` --- ## 性能相关 ### Q13: 这个方案的性能瓶颈在哪里? **A:** 主要瓶颈点: 1. **SSL 握手**:RSA/ECDHE 密钥交换是 CPU 密集操作,可使用 `ssl_session_cache` 复用 2. **stream 转发**:几乎无额外开销(TCP 层直通) 3. **文件描述符**:高并发时需要足够的 fd 配额 4. **树莓派 CPU**:ARM 处理器 SSL 性能较弱,可通过 session cache 和硬件加速缓解 ### Q14: SSL Session Cache 大小怎么计算? **A:** - `shared:SSL_mqtt:5m` 表示 5MB 共享内存 - 每个 SSL session 约 500 bytes - 5MB ≈ 10,000 个并发 SSL session - 4 个服务组总计约 25MB,对 8GB 树莓派完全可接受 --- ## 故障排查 ### Q15: 如何判断 HTTP 流量是否正确到达跳转 server? **A:** 查看 stream 访问日志: ```bash tail -f /var/log/nginx/stream_access_40715.log # 正常 HTTP 访问应该看到 upstream 指向跳转端口 ``` ### Q16: 301 跳转后浏览器显示 "重定向循环" 怎么办? **A:** 常见原因: 1. HTTP 跳转 server 的 `return 301` URL 中端口写错了 2. HTTPS server 意外把请求又转回了 HTTP 3. 后端应用的 `siteurl` 配置为 HTTP 排查: ```bash curl -v http://mqtt.zhonjin.com:40715 2>&1 | grep Location # 确认 Location 是 https:// 开头且端口正确 ``` ### Q17: `nginx -t` 报错 "include file not found" 怎么办? **A:** 1. 检查文件是否存在:`ls -la /etc/nginx/conf.d/zhonjin_http.conf` 2. 检查目录是否存在:`ls -la /etc/nginx/stream.d/` 3. 检查文件权限:确保 nginx 用户(www-data)有读权限 4. 检查路径拼写 ### Q18: stream include 不生效? **A:** - 确认 nginx 版本 ≥ 1.26(支持 stream 块内 include) - 确认 include 路径正确 - 确认 `nginx -t` 通过 - 检查 stream 块的 include 是否在正确位置(stream 块内部) --- ## 端口扩展 ### Q19: 新增端口映射时,如何确保不影响已有服务? **A:** 遵循检查清单: 1. ✅ 新增的 map 变量名不与已有的冲突 2. ✅ 新增的 HTTPS/HTTP 内部端口不与已有的冲突 3. ✅ SSL session cache 名称不重复 4. ✅ stream 日志文件路径不重复 5. ✅ 防火墙放行新端口 6. ✅ `nginx -t` 通过后再 reload ### Q20: 端口范围有没有限制? **A:** - 有效端口范围:1-65535 - 1024 以下需要 root 权限监听 - 建议使用 10000-65535 范围的非特权端口 - 避免与常见服务端口冲突 --- ## 树莓派部署 ### Q21: 树莓派跑这个方案会不会性能不足? **A:** 树莓派 5 8G 完全够用: 1. stream 模块是 TCP 层直通,CPU 开销极低 2. 树莓派 5 的 Cortex-A76 支持 ARMv8 crypto 扩展 3. 个人/小型团队场景,并发量通常在 100 以内 4. 实际测试:树莓派 4 就能处理 500+ SSL req/s ### Q22: SD 卡会被日志写坏吗? **A:** 长期高频写入确实会影响 SD 卡寿命。建议: 1. 将日志目录挂载为 tmpfs(内存文件系统) 2. 或使用外接 SSD/USB 驱动器存储日志 3. 配置 logrotate 限制日志大小 4. 对于非关键场景,可以关闭 stream access_log ### Q23: 树莓派上编译 Nginx 需要多长时间? **A:** - 树莓派 5(Cortex-A76):约 5-10 分钟(`make -j4`) - 树莓派 4(Cortex-A72):约 10-20 分钟 - 树莓派 3:约 30-60 分钟 - 建议直接使用 APT 安装,避免编译 --- *新问答将按类别追加到对应章节中。*