Featured image of post Nginx stream 四层 TCP 代理实战:MSTSC 远程桌面透传与排错

Nginx stream 四层 TCP 代理实战:MSTSC 远程桌面透传与排错

Nginx stream 模块四层代理配置、自定义 log_format 抓客户端真实 IP、buffer 调优、upstream 连接重置错误、HTTP 反代 buffer 不足 9 大配置详解

背景

Nginx 早期是 HTTP(七层)反代神器,但从 1.9.0(2015 年)开始,ngx_stream_core_module 提供了四层(TCP/UDP)代理——能代理任何 TCP 协议:

  • RDP(Windows 远程桌面 3389)
  • MySQL、Redis、MongoDB
  • SSH
  • SMTP、POP3
  • 游戏服务器
  • 自定义 TCP 协议

与 HAProxy/LVS 对比

维度Nginx streamHAProxyLVS
学习成本最低
性能中等最强
七层能力同时支持同时支持不支持
适用规模中小(< 5w QPS)超大

典型场景:公司有几台 Windows 服务器,通过一台 Linux 反代把不同端口的 RDP 流量透明转发到不同后端,外网只暴露一个 VIP。


一、Nginx stream 模块基础

1.1 编译参数

stream 模块不是默认编译的,需要 --with-stream

1
2
3
nginx -V
# 看是否有 --with-stream
# 如果没有,要重编译

重编译步骤:

1
2
3
4
5
6
7
# 看现有参数
nginx -V
# 在最后加 --with-stream
./configure <原参数> --with-stream
make
# 不要 make install(会覆盖),手动替换二进制
cp objs/nginx /usr/local/nginx/sbin/nginx

1.2 配置结构

/etc/nginx/nginx.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 顶层 stream 块(与 http 同级)
stream {
    log_format proxy '$remote_addr [$time_local] '
                     '$protocol $status $bytes_sent $bytes_received '
                     '$session_time "$upstream_addr" '
                     '$upstream_bytes_sent $upstream_bytes_received '
                     '$upstream_connect_time';
    
    # 引入 conf.d/ 下的所有 stream 配置
    include /etc/nginx/stream/*.conf;
}

/etc/nginx/stream/rdp.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
upstream rdp_pool {
    hash $remote_addr consistent;  # 同一客户端固定连同一后端
    server 10.0.0.10:3389 max_fails=3 fail_timeout=30s;
    server 10.0.0.11:3389 max_fails=3 fail_timeout=30s;
}

server {
    listen 3390;            # 外网端口
    proxy_pass rdp_pool;    # 转发到 upstream
    proxy_connect_timeout 10s;
    proxy_timeout 300s;
    
    # 自定义日志
    access_log /var/log/nginx/rdp_access.log proxy;
    error_log  /var/log/nginx/rdp_error.log;
}

关键点

  • stream 块在顶层(与 http 同级),不在 http 里面
  • 监听端口不能与 http 块冲突
  • 没有 server_name、没有 location(四层只看 IP:Port)

二、MSTSC 远程桌面透传实战

场景:公网 IP 47.104.152.64,Windows 内网 RDP 真实地址 10.0.0.10:3389

2.1 完整配置

1
2
3
4
5
6
7
8
9
stream {
    log_format proxy '[$time_local] $protocol status:$status, '
                     'bytes client:$bytes_sent/$bytes_received $session_time, '
                     'client: $remote_addr, upstream:"$upstream_addr", '
                     'bytes upstream:$upstream_bytes_sent $upstream_bytes_received '
                     '$upstream_connect_time';
    
    include /etc/nginx/stream/*.conf;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# /etc/nginx/stream/rdp.conf
upstream mstsc {
    hash $remote_addr consistent;
    server 10.0.33.101:3389 max_fails=3 fail_timeout=30s;
}

server {
    error_log  /var/log/nginx/101_3399_error.log;
    access_log /var/log/nginx/101_3399_access.log proxy;
    listen 3399;                # 外网访问 3399
    proxy_connect_timeout 10s;
    proxy_timeout 300s;
    proxy_pass mstsc;
}

客户端连接mstsc /v:47.104.152.64:3399

2.2 日志样例

1
2
3
[25/Apr/2017:17:55:57 +0800] TCP status:200, bytes client:103/122 10.671, 
client: 192.168.3.218, upstream: "192.168.1.176:59001" 
bytes upstream:122/103 0.000

字段解读

  • bytes client:103/122:客户端发送 103 字节,接收 122 字节
  • client: 192.168.3.218:客户端真实 IP(不是 47.104.152.64)
  • upstream: "192.168.1.176:59001":Nginx 主动连到 Windows 的源端口
  • bytes upstream:122/103:Nginx 转发给后端的字节数

三、Nginx HTTP 反代的 9 大 buffer 调优

常见 warning(Nginx error.log):

1
an upstream response is buffered to a temporary file

原因:Nginx 默认的 buffer 太小,每个请求的缓存不够,请求头 header 太大或响应体超过 buffer 时,写入磁盘临时文件,造成 IO 飙升,访问卡顿。

3.1 完整 buffer 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
http {
    # client body(上传)
    client_max_body_size       2048m;       # 最大上传 2GB
    client_body_buffer_size    1024k;       # body 缓冲 1MB
    
    # proxy buffer
    proxy_buffer_size          256k;        # header 缓冲 256K
    proxy_buffering            on;          # 开启缓冲(默认 on)
    proxy_buffers              64 128k;     # 64 个 128K 缓冲(共 8MB)
    proxy_busy_buffers_size    512k;        # 忙缓冲 512K(不能 > proxy_buffers 总和/2)
    
    # FastCGI buffer(PHP-FPM)
    fastcgi_buffer_size        512k;        # header 缓冲
    fastcgi_buffers            6 512k;      # 6 个 512K 缓冲(共 3MB)
    fastcgi_busy_buffers_size  512k;        # 忙缓冲
    fastcgi_temp_file_write_size 512k;      # 临时文件写入块大小
    fastcgi_intercept_errors   on;          # 拦截错误(4xx/5xx 自己处理)
}

3.2 各项调优解释

配置作用调优建议
client_max_body_size最大请求体(POST 上传)按业务定,文件上传建议 1g+
client_body_buffer_size客户端 body 缓冲16k ~ 1m
proxy_buffer_size后端响应缓冲4k ~ 32k,大响应头可调到 128k
proxy_buffers后端响应缓冲(数量 × 单个大小)通常 8 16k64 128k
proxy_busy_buffers_size正在发送的缓冲上限必须 < proxy_buffers 总和的 1/2
proxy_temp_file_write_size临时文件单次写入大小默认 8k,调大可减少 IO 次数
fastcgi_intercept_errorsNginx 拦截 FastCGI 错误on 配合 error_page 自定义错误页

3.3 调优公式

总 buffer = proxy_buffer_size + proxy_buffers × 数量

例:proxy_buffers 64 128k = 64 × 128K = 8MB 单连接缓存

繁忙 buffer = proxy_busy_buffers_size × 2 ≤ 总 buffer

例:512k × 2 = 1MB ≤ 8MB


四、Connection reset by peer 排错

完整错误

1
2
3
4
recv() failed (104: Connection reset by peer) 
while proxying and reading from upstream, 
client: 89.248.163.87, server: 0.0.0.0:3399, 
upstream: "10.8.33.101:3389"

104 错误 = TCP RST,连接被对端强制关闭。

常见原因

  1. 后端服务主动断开(如 Windows RDP 服务异常)
  2. 后端超时未响应,TCP keepalive 触发 RST
  3. 后端资源耗尽(CPU 100%、连接数打满)
  4. Nginx 与后端的网络问题(中间设备丢包)

4.1 调优配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
upstream bigdata {
    server 10.0.20.1:18018;
    server 10.0.20.2:18018;
    server 10.0.20.3:18018;
    server 10.0.20.4:18018;
    keepalive 100;       # Nginx 与上游保持的长连接数
}

location / {
    proxy_pass http://bigdata;
    
    # 加大超时(默认 60s 偏短)
    proxy_connect_timeout   120;
    proxy_send_timeout      120;
    proxy_read_timeout      120;
    
    # 关键:启用 HTTP/1.1 + 清理 Connection header
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}

关键点

配置作用
keepalive 100Nginx 与每个上游保持 100 个长连接(连接复用)
proxy_http_version 1.1必须 1.1——HTTP/1.0 不支持 keep-alive
proxy_set_header Connection ""清掉 Connection: close,让 keep-alive 生效

为什么 HTTP/1.1 + Connection "" 必加

HTTP/1.0 的 Connection: keep-alive可选的,HTTP/1.1 默认 keep-alive 但 Connection: close 可以强制关闭。Nginx 默认往上游发的 Connection: close(不重用连接),所以必须显式清掉


五、实战:MySQL stream 代理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
stream {
    upstream mysql_pool {
        server 10.0.0.10:3306;
        server 10.0.0.11:3306;
    }
    
    server {
        listen 33306;
        proxy_pass mysql_pool;
        proxy_connect_timeout 5s;
        proxy_timeout 600s;
    }
}

客户端连接

1
mysql -h <nginx-ip> -P 33306 -u root -p

注意:MySQL 认证是基于 IP 的,代理后所有客户端都是 nginx-ip,可能触发 host 'nginx-ip' is not allowed对策

  • MySQL 授权时用 'root'@'%'
  • 或在 MySQL 端用 skip-name-resolve

六、实战:Redis stream 代理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
stream {
    upstream redis_pool {
        server 10.0.0.10:6379;
    }
    
    server {
        listen 36379;
        proxy_pass redis_pool;
        proxy_connect_timeout 5s;
        proxy_timeout 300s;
    }
}

Redis 协议简单,stream 代理几乎零问题


七、SSL/TLS over stream

stream 块也支持 SSL(需 --with-stream_ssl_module):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
stream {
    upstream mysql_ssl {
        server 10.0.0.10:3306;
    }
    
    server {
        listen 33307 ssl;
        proxy_pass mysql_ssl;
        ssl_certificate     /etc/nginx/ssl/server.crt;
        ssl_certificate_key /etc/nginx/ssl/server.key;
        ssl_protocols       TLSv1.2 TLSv1.3;
    }
}

典型场景:MySQL 客户端启 SSL,但不想让客户端直接连真实 MySQL,走 Nginx 反代统一证书。


八、负载均衡算法

 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
upstream backend {
    # 1. 轮询(默认)
    # server 10.0.0.10:8080;
    # server 10.0.0.11:8080;
    
    # 2. 加权轮询
    # server 10.0.0.10:8080 weight=3;
    # server 10.0.0.11:8080 weight=1;
    
    # 3. ip_hash(会话保持)
    # ip_hash;
    # server 10.0.0.10:8080;
    # server 10.0.0.11:8080;
    
    # 4. consistent hash(一致性 hash,加减节点影响小)
    hash $remote_addr consistent;
    server 10.0.0.10:8080;
    server 10.0.0.11:8080;
    
    # 5. least_conn(最闲)
    # least_conn;
    # server 10.0.0.10:8080;
    
    # 6. random
    # random;
}

九、健康检查与失败处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
upstream backend {
    server 10.0.0.10:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
    # 健康检查:失败 3 次标记为 down,30 秒后再试
}

server {
    listen 80;
    proxy_pass backend;
    proxy_next_upstream error timeout invalid_header http_502 http_503;
    # 后端 error/timeout/502/503 时,自动切到下一个 upstream
    proxy_next_upstream_tries 2;
    proxy_next_upstream_timeout 10s;
}

max_failsfail_timeout

  • 30 秒内失败 3 次 → 标记 down
  • 30 秒后再尝试(如果还失败,继续 down)

proxy_next_upstream

  • error:连接错误
  • timeout:超时
  • invalid_header:响应头无效
  • http_502/503/504:HTTP 错误码
  • off:禁用

十、常见错误

10.1 stream 块识别为 HTTP

症状:Nginx 启动报 unknown directive "stream"

原因:编译时--with-stream

对策:重编译 nginx 加 --with-stream,参考 1.1 节。

10.2 listen 端口冲突

1
bind() to 0.0.0.0:3399 failed (98: Address already in use)

对策

1
2
3
ss -tan | grep 3399
# 看哪个进程占用
lsof -i :3399

10.3 客户端真实 IP 丢失

默认情况:后端看到的客户端 IP 是 Nginx 内网 IP(不是真实客户端)。

对策仅限 HTTP,TCP 协议做不到):

1
2
3
4
5
location / {
    proxy_pass http://backend;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

stream 块没有 proxy_set_header!要传真实 IP 必须用应用层协议(如 HTTP 的 X-Forwarded-For、MySQL 的账号 IP 白名单)。

10.4 大量 TIME_WAIT

症状netstat -an | grep TIME_WAIT | wc -l 几万。

对策

1
2
3
4
5
# 开启 TIME_WAIT 复用
sysctl -w net.ipv4.tcp_tw_reuse=1

# 缩短 TIME_WAIT 时间(默认 60s)
sysctl -w net.ipv4.tcp_fin_timeout=30

小结

Nginx stream 模块让 Nginx 从七层反代进化为"四/七层通吃"

  1. 编译加 --with-stream
  2. stream 块与 http 同级
  3. listen + proxy_pass 简单粗暴
  4. keepalive + HTTP/1.1 + Connection "" 是 HTTP 反代性能关键
  5. buffer 调优避免写临时文件
  6. 客户端真实 IP(四层)传不到后端,靠应用层协议

生产建议

  • 七层反代:用 Nginx,配置友好
  • 四层 1w-5w QPS:用 Nginx stream
  • 四层 5w+ QPS:用 LVS DR 或 HAProxy
  • 多协议混合:Nginx 统一管(HTTP 在 http 块,TCP 在 stream 块)

下一步

  • 用 Nginx stream + Let’s Encrypt 做 TLS 反代
  • 上 HAProxy 做更细的健康检查
  • 用 Envoy / APISIX 做云原生时代的四/七层代理

2024 视角:Nginx 仍是 1.25+ / 1.27+ 时代的王者

2013 那篇的 ngx_stream_core_module 仍是 Nginx 的核心特性。2024 视角下

一、Nginx 1.25 / 1.27(2024 主流)

  • Nginx 1.25.x(2023-05 起的 mainline):实验新特性。
  • Nginx 1.27.x(2024-08):新 mainline,引入 HTTP/3 over QUIC 完整支持。
  • Nginx Plus R32(2024):商业版新增 Dynamic Configuration via APIActive Health Checks 增强。
1
2
3
# 看版本
nginx -v
# nginx version: nginx/1.27.0

二、QUIC / HTTP/3 已成"必选项"

  • 2013 那篇完全没提 QUIC。2024 视角下 QUIC / HTTP/3 已是性能优化标配:
    • 握手 0-RTT(比 TLS 1.3 还快)
    • 连接迁移(IP 变化不重连)
    • 多路复用(解决 HTTP/2 队头阻塞)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Nginx 1.25+ 启用 HTTP/3
server {
    listen 443 ssl;
    http2 on;
    
    # 启用 HTTP/3(同时监听 UDP 443)
    add_header Alt-Svc 'h3=":443"; ma=86400';
    
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_early_data on;
    ssl_protocols TLSv1.3;
}

# Nginx 1.27+ 直接
server {
    listen 443 quic reuseport;
    listen 443 ssl;
    http2 on;
    add_header Alt-Svc 'h3=":443"; ma=86400';
}

三、buffer 调优的"现代"配方

  • 2013 那篇给的 proxy_buffer_size 256k / proxy_buffers 64 128k 仍是有效。
  • 2024 实践
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 大文件 / 视频流
proxy_buffer_size        1m;
proxy_buffers           8 1m;          # 8 个 1M(共 8M)
proxy_busy_buffers_size 2m;
proxy_temp_file_write_size 4m;

# API 服务(小响应)
proxy_buffer_size        16k;
proxy_buffers           8 16k;
proxy_busy_buffers_size 32k;
  • 新指令
    • proxy_socket_keepalive on:向上游发 TCP keep-alive(避免 TIME_WAIT 堆积)
    • proxy_pass_request_body off:透传大 body 优化
    • proxy_force_ranges on:强制 Range 支持

四、Connection reset by peer 的 2024 排查清单

2013 那篇给的 proxy_http_version 1.1 + Connection "" + keepalive 100 仍是标准动作。2024 升级

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
upstream backend {
    server 10.0.20.1:18018 resolve;
    server 10.0.20.2:18018 resolve;
    keepalive 32;                # 适当调小(旧值 100 过大)
    keepalive_requests 1000;     # 单连接最多 1000 请求后关闭(防内存泄漏)
    keepalive_timeout 60s;
}

location / {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_connect_timeout   5s;     # 比 120s 短
    proxy_send_timeout      60s;
    proxy_read_timeout      60s;
    proxy_socket_keepalive  on;     # 2024 新
    proxy_next_upstream     error timeout http_502 http_503;
    proxy_next_upstream_tries 2;
}
  • 新调试工具
    • nginx -T:dump 完整配置(include 全部展开)
    • error.log debug:详细 debug 日志
    • stub_status:基础统计
    • /usr/lib/nginx/modules/ngx_http_vhost_traffic_status(淘宝开源):流量详情

五、stream 块在 K8s 时代的"角色"

  • 2024 趋势:Nginx stream 在 K8s 时代被 Envoy 替代。
  • Nginx Ingress Controller(K8s 标准 Ingress 实现):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# k8s-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp
            port:
              number: 80
  • 优势:Nginx Ingress Controller = Nginx + K8s 自动化(自动 reload、自动证书、自动 upstream)。

六、动态 upstream 的"现代"实现

  • 2013 那篇的 upstream 是静态配置。
  • 2024 现代姿势upstream {} 动态解析(DNS + Service Discovery):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 动态 upstream(Nginx Plus 特性,开源版部分支持)
upstream backend {
    zone backend 64k;
    server backend-service.default.svc.cluster.local:80 resolve;
    resolver kube-dns.kube-system.svc.cluster.local valid=30s;
}

# 通过 API 动态加 backend
curl -X POST http://127.0.0.1:8080/api/version/http/upstreams/backend/servers/ \
     -d '{"server":"10.0.1.50:80"}'
  • Nginx 开源版(1.27+)也支持 resolver + resolve 指令,SVC 后端自动发现

七、SSL/TLS 的"现代"最佳实践

  • 2013 那篇的 ssl_protocols TLSv1 TLSv1.1 TLSv1.2 已经被2024 安全规范淘汰
  • 2024 强制
1
2
3
4
5
6
7
8
ssl_protocols       TLSv1.2 TLSv1.3;
ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers on;
ssl_session_cache   shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
  • OCSP Stapling:减少客户端验证延迟
  • TLS 1.3 强制:禁用 TLS 1.0/1.1

八、Nginx + 服务网格的"混合部署"

  • 2024 大量 K8s 项目不再用 Nginx 反代——用 Istio / Linkerd 自带 Envoy。
  • Nginx 仍在用的场景:
    • 边缘节点(CDN 边缘)
    • 物理机 / 虚拟机(不在 K8s 里)
    • Web 服务器(静态资源)
    • API 网关(Apigee / Kong 基于 Nginx)
    • Sidecar 替代(轻量场景,Nginx sidecar 比 Envoy 资源占用更少)

九、Nginx 商业替代

  • F5 NGINX Plus(商业版):Dynamic Configuration API、Active Health Checks、JWT 验证、WAF
  • OpenResty(章亦春):Lua 脚本扩展——2024 仍是国内"高阶 Nginx 玩法"主流
  • Caddy:自动 HTTPS(Let’s Encrypt 内置)
  • Traefik:云原生时代的"动态 Nginx"

源文档

  • os/linux/第三方tools/dev/反向代理/Nginx/nginx.md(stream TCP 代理 + 自定义日志)
  • os/linux/第三方tools/dev/反向代理/Nginx/问题.md(buffer 调优 + Connection reset by peer)
使用 Hugo 构建
主题 StackJimmy 设计