Nginx Stream模块配置及Socket转发问题排查

8 次阅读

一、Nginx Stream模块是什么

Nginx 不仅可以处理 HTTP/HTTPS 请求,还可以借助 Stream 模块代理 TCP、UDP 等四层网络流量。当后端服务不是标准 HTTP 服务,而是 Socket、数据库、消息队列、游戏服务或其他 TCP 服务时,Stream 就非常适合用于端口转发。

典型场景如下:

客户端
   │
   │ TCP连接
   ▼
Nginx:9000
   │
   │ Stream转发
   ▼
后端服务器:8080

客户端只需要连接 Nginx 暴露的端口,Nginx 再将 TCP 连接转发到指定的后端地址。

与 http {} 中的 proxy_pass 不同,Stream 工作在传输层,不需要理解 HTTP 请求内容,因此不会修改 HTTP Header,也不会依赖 URL、Host 等 HTTP 信息。


二、检查Nginx是否支持Stream模块

配置 Stream 之前,首先需要确认当前 Nginx 是否编译了 Stream 模块。

执行:

Bash
nginx -V

查看输出中的编译参数,例如:

--with-stream

如果能够看到:

--with-stream=dynamic

则表示 Stream 以动态模块方式提供。

部分发行版可能需要单独安装对应模块。以 Debian/Ubuntu 系统为例,可以先检查相关模块包:

Bash
apt search nginx-module-stream

CentOS、Rocky Linux、AlmaLinux 等系统则可以通过:

Bash
dnf search nginx stream

或者:

Bash
yum search nginx stream

进行确认。

如果使用的是源码编译的 Nginx,可以在编译时加入:

Bash
./configure --with-stream
make
make install

安装完成后再次执行:

Bash
nginx -V

确认编译参数是否生效。

需要注意的是,stream 并不是放在 http {} 里面使用的。它属于 Nginx 的顶层配置模块。


三、基础Stream TCP转发配置

最简单的 TCP Socket 转发配置如下:

Nginx
stream {
    server {
        listen 9000;
        proxy_pass 192.168.1.100:8080;
    }
}

这段配置的含义非常直观:

客户端 → Nginx:9000 → 192.168.1.100:8080

客户端访问:

Bash
telnet nginx服务器IP 9000

或者:

Bash
nc nginx服务器IP 9000

建立连接后,Nginx 会把 TCP 数据转发给:

192.168.1.100:8080

如果后端是一个自定义 Socket 服务,这种方式同样适用。


四、将Stream配置放到独立文件中

生产环境中通常不建议把大量 Stream 配置全部写入 nginx.conf,可以通过 include 拆分配置。

主配置:

Nginx
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
}

stream {
    include /etc/nginx/stream.d/*.conf;
}

然后创建:

Bash
mkdir -p /etc/nginx/stream.d

例如:

Bash
vim /etc/nginx/stream.d/socket.conf

写入:

Nginx
server {
    listen 9000;
    proxy_connect_timeout 5s;
    proxy_timeout 60s;
    proxy_pass 192.168.1.100:8080;
}

这种结构更加方便维护多个 TCP 服务。

例如:

/etc/nginx/
├── nginx.conf
└── stream.d/
    ├── mysql.conf
    ├── socket.conf
    └── redis.conf

五、Stream模块配置中的关键参数

1. listen

listen 用于定义 Nginx 对外监听的端口。

Nginx
listen 9000;

如果只希望监听指定 IP:

Nginx
listen 192.168.1.10:9000;

IPv6 环境也可以配置:

Nginx
listen [::]:9000;

如果需要 UDP 转发,则需要显式声明:

Nginx
listen 9000 udp;

TCP 和 UDP 的监听方式不能混为一谈。


2. proxy_pass

proxy_pass 指定 Socket 的后端地址:

Nginx
proxy_pass 192.168.1.100:8080;

也可以使用域名:

Nginx
proxy_pass backend.example.com:8080;

如果后端存在多个服务器,可以配合 upstream:

Nginx
stream {
    upstream socket_backend {
        server 192.168.1.100:8080;
        server 192.168.1.101:8080;
    }

    server {
        listen 9000;
        proxy_pass socket_backend;
    }
}

这样可以实现 TCP 层面的负载均衡。


3. proxy_connect_timeout

控制 Nginx 与后端建立连接时的超时时间:

Nginx
proxy_connect_timeout 5s;

如果后端服务器不可访问,超过该时间仍然无法建立连接,Nginx 就会返回连接失败。


4. proxy_timeout

控制代理连接的超时时间:

Nginx
proxy_timeout 60s;

Socket 服务如果需要保持长连接,应根据业务特点适当增大这个值。

例如某些长连接服务可能需要:

Nginx
proxy_timeout 1h;

不能简单套用 HTTP 服务的短超时配置,否则可能出现 Socket 连接莫名断开的情况。


六、完整的Socket转发配置示例

假设有一个 Socket 服务运行在:

10.10.10.20:7000

希望通过 Nginx 的:

0.0.0.0:9000

对外提供访问,可以配置:

Nginx
stream {
    server {
        listen 9000;
        proxy_connect_timeout 10s;
        proxy_timeout 300s;

        proxy_pass 10.10.10.20:7000;
    }
}

修改完成后不要直接重启 Nginx,先检查配置:

Bash
nginx -t

正常情况下会看到:

syntax is ok
test is successful

确认无误后执行:

Bash
nginx -s reload

或者使用 systemd:

Bash
systemctl reload nginx

七、Nginx Stream配置不生效的常见原因

1. Stream写进了http配置块

错误写法:

Nginx
http {
    stream {
        server {
            listen 9000;
            proxy_pass 192.168.1.100:8080;
        }
    }
}

这种结构是错误的。

正确方式应该是:

Nginx
http {
    ...
}

stream {
    server {
        listen 9000;
        proxy_pass 192.168.1.100:8080;
    }
}

stream 与 http 应处于同一级配置层级。


2. Nginx没有编译Stream模块

如果执行:

Bash
nginx -t

出现类似:

unknown directive "stream"

通常需要检查 Nginx 是否包含 Stream 模块。

执行:

Bash
nginx -V 2>&1 | grep stream

如果没有相关信息,就需要安装对应模块或重新编译 Nginx。


3. 动态模块没有加载

如果使用动态 Stream 模块,可能需要在配置文件顶部加载模块,例如具体模块文件路径取决于安装方式:

Nginx
load_module modules/ngx_stream_module.so;

然后再配置:

Nginx
stream {
    ...
}

如果模块文件不存在,不能直接照搬这个路径,应先通过安装包或系统文件确认实际位置。


八、Socket转发连接失败的排查方法

Socket 转发出现问题时,不要只检查 Nginx 配置。应该按照“监听端口 → Nginx → 网络 → 后端服务”的路径逐层排查。

第一步:确认Nginx监听端口

执行:

Bash
ss -lntp | grep 9000

或者:

Bash
netstat -lntp | grep 9000

正常情况下应该看到类似:

LISTEN 0 511 0.0.0.0:9000 0.0.0.0:* users:(("nginx",pid=1234,fd=8))

如果完全没有结果,说明 Nginx 并没有监听这个端口。

优先检查:

Bash
nginx -t

以及:

Bash
nginx -T

nginx -T 特别有用,因为它会输出最终加载的完整配置,可以确认 stream 配置文件是否真的被 include。


第二步:检查后端端口

在 Nginx 服务器上直接连接后端:

Bash
nc -vz 10.10.10.20 7000

如果返回:

Connection succeeded

说明 Nginx 到后端的 TCP 网络基本正常。

如果出现:

Connection refused

重点检查后端服务是否启动,以及服务是否监听正确端口。

执行:

Bash
ss -lntp | grep 7000

如果服务只监听:

127.0.0.1:7000

那么其他服务器无法直接访问。

需要根据实际部署情况调整监听地址,例如:

0.0.0.0:7000

或者后端服务器的实际内网 IP。


第三步:检查防火墙

如果 Nginx 监听正常,但客户端无法连接,需要检查防火墙。

Linux 上可以查看:

Bash
firewall-cmd --list-ports

或者:

Bash
iptables -L -n

Ubuntu 使用 UFW 时可以检查:

Bash
ufw status

例如需要开放 TCP 9000:

Bash
firewall-cmd --permanent --add-port=9000/tcp
firewall-cmd --reload

如果服务器部署在云平台,还必须检查云安全组、网络 ACL 等规则。


九、使用telnet和nc定位Socket问题

客户端首先可以执行:

Bash
nc -vz NGINX_IP 9000

如果看到:

Connection refused

通常说明目标端口没有监听,或者中间设备主动拒绝连接。

如果出现:

Connection timed out

则更应该检查防火墙、安全组、路由以及网络连通性。

也可以使用:

Bash
telnet NGINX_IP 9000

成功建立连接只能证明 TCP 三次握手完成,并不能证明后端业务协议一定正常。

例如:

客户端 → Nginx

已经连接成功,但:

Nginx → 后端Socket

可能仍然失败。

因此还应该在 Nginx 服务器上单独测试:

Bash
nc -vz BACKEND_IP BACKEND_PORT

十、通过tcpdump确认数据到底走到了哪里

遇到“端口能连接,但业务没有响应”的问题时,tcpdump 非常有效。

在 Nginx 服务器执行:

Bash
tcpdump -i any tcp port 9000

观察客户端是否真的连接到了 Nginx。

同时可以抓取后端端口:

Bash
tcpdump -i any host 10.10.10.20 and port 7000

如果能够看到客户端与 9000 端口建立连接,却完全看不到 Nginx 与 7000 端口建立连接,那么问题大概率集中在 Nginx Stream 配置、后端地址解析或 Nginx 到后端的网络连接上。

如果两个方向都有 TCP 数据包,则可以继续检查应用层协议。


十一、检查SELinux对Socket转发的影响

在 CentOS、RHEL、Rocky Linux、AlmaLinux 等系统中,如果启用了 SELinux,还需要注意安全策略。

查看状态:

Bash
getenforce

如果返回:

Enforcing

可以检查相关日志:

Bash
ausearch -m AVC -ts recent

某些情况下,Nginx 作为网络代理访问后端 TCP 服务可能受到 SELinux 策略限制。

可以通过日志确认是否存在拒绝记录,而不是直接关闭 SELinux。

生产环境中不建议为了排查问题长期执行:

Bash
setenforce 0

如果临时关闭后问题消失,也应该进一步配置正确的 SELinux 策略,而不是把关闭 SELinux 当作最终解决方案。


十二、Stream代理如何保留客户端真实IP

TCP 代理与 HTTP 代理存在一个重要区别:Stream 默认不会像 HTTP 那样通过 X-Forwarded-For 传递客户端地址。

如果后端 Socket 服务需要知道客户端真实 IP,可以考虑使用 Proxy Protocol。

Nginx:

Nginx
stream {
    server {
        listen 9000 proxy_protocol;
        proxy_pass 10.10.10.20:7000;
        proxy_protocol on;
    }
}

不过这里有一个关键问题:后端服务必须支持 Proxy Protocol。

如果后端程序并不识别 Proxy Protocol,那么 Nginx 添加的 Proxy Protocol 头部可能被后端当成业务数据,从而导致协议解析失败。

因此配置之前必须确认:

Nginx → 后端Socket服务

两端是否约定使用 Proxy Protocol。


十三、Socket长连接异常断开的排查

一些 Socket 服务需要维持数分钟甚至数小时的 TCP 长连接。

例如:

Nginx
stream {
    server {
        listen 9000;
        proxy_connect_timeout 10s;
        proxy_timeout 1h;
        proxy_pass 10.10.10.20:7000;
    }
}

如果客户端连接一段时间后突然断开,应检查以下几个层面:

  1. proxy_timeout 是否过短;

  2. 后端程序是否主动关闭连接;

  3. 防火墙是否存在连接跟踪超时;

  4. 云负载均衡是否存在空闲连接超时;

  5. 客户端是否发送心跳;

  6. 后端 Socket 服务是否存在自身的空闲连接策略。

尤其需要注意,TCP 连接建立成功并不代表可以永久保持。如果中间网络设备存在 idle timeout,而业务又长时间没有数据交换,就可能出现连接被清理的情况。


十四、使用upstream管理多个Socket后端

当 Socket 服务存在多个实例时,可以使用 Stream upstream:

Nginx
stream {
    upstream socket_servers {
        server 10.10.10.20:7000;
        server 10.10.10.21:7000;
        server 10.10.10.22:7000;
    }

    server {
        listen 9000;
        proxy_connect_timeout 5s;
        proxy_timeout 300s;
        proxy_pass socket_servers;
    }
}

这样客户端只需要访问:

Nginx:9000

Nginx 会在后端服务器之间进行连接分配。

对于 TCP 长连接,需要特别理解负载均衡的特点:一次 TCP 连接建立之后,通常会固定对应一个后端连接,并不会把同一个连接中的数据实时切换到另一台服务器。

因此后端服务是否支持连接级负载均衡,需要结合实际业务架构判断。


十五、DNS解析导致的Socket转发问题

如果配置:

Nginx
proxy_pass backend.example.com:7000;

需要注意域名解析行为和具体 Nginx 配置方式。

生产环境中,如果后端地址经常变化,仅仅修改 DNS 记录并不一定意味着已经实现了实时动态切换。应根据 Nginx 版本、解析配置以及业务需求设计 DNS 解析策略。

排查时可以先确认:

Bash
getent hosts backend.example.com

或者:

Bash
nslookup backend.example.com

再测试:

Bash
nc -vz backend.example.com 7000

如果 DNS 解析到了错误地址,Nginx Stream 本身配置正确也无法正常转发。


十六、常见问题快速定位表

现象优先检查方向
unknown directive "stream"Stream模块是否安装或加载
Nginx启动失败nginx -t、配置层级、模块
9000端口没有监听Stream配置是否被加载
客户端 Connection refused监听端口、防火墙
客户端 Connection timed out安全组、路由、防火墙
Nginx无法连接后端后端服务、端口、防火墙
TCP连接成功但业务无响应Socket协议、Proxy Protocol
长连接频繁断开proxy_timeout、心跳、网络设备
后端无法获取真实IPProxy Protocol配置
修改配置后没有效果nginx -T确认实际加载配置
多后端连接异常upstream及后端健康状态

十七、一套完整的排查流程

遇到 Nginx Stream Socket 转发异常,可以按照下面的顺序处理:

① nginx -t
      ↓
② nginx -T
      ↓
③ ss -lntp检查Nginx监听端口
      ↓
④ nc测试客户端 → Nginx
      ↓
⑤ nc测试Nginx → 后端
      ↓
⑥ ss检查后端监听状态
      ↓
⑦ 检查防火墙和云安全组
      ↓
⑧ 检查SELinux
      ↓
⑨ tcpdump抓包
      ↓
⑩ 检查Socket协议和长连接参数

这种排查方式比反复修改 Nginx 配置更加高效。

尤其是:

Bash
nginx -t

用于确认配置语法;

Bash
nginx -T

用于确认实际生效配置;

Bash
ss -lntp

用于确认端口监听;

Bash
nc -vz

用于确认 TCP 连通性;

Bash
tcpdump

用于确认真实网络数据包。

几个命令配合使用,基本可以快速确定故障发生在 Nginx 配置、端口监听、网络策略还是后端 Socket 服务本身。


十八、生产环境配置建议

Nginx Stream 配置虽然看起来简单,但生产环境中仍然需要注意几个细节。

首先,不要随意将所有端口暴露到公网。listen 0.0.0.0:9000 意味着 Nginx 会在所有 IPv4 地址上监听,应结合防火墙和安全组限制访问来源。

其次,长连接业务不要机械地使用很短的 proxy_timeout。应该根据业务心跳周期和网络设备的连接超时策略进行设置。

再次,涉及 Proxy Protocol 时必须确保后端程序支持,否则容易出现“TCP连接成功,但业务协议解析失败”的情况。

最后,修改 Nginx 配置后建议固定执行:

Bash
nginx -t && nginx -s reload

这样可以先验证配置,再重新加载服务,避免错误配置直接导致 Nginx 无法启动或重新加载失败。

Nginx Stream 的核心价值并不是简单地“把一个端口转到另一个端口”,而是在四层网络环境中提供稳定的 TCP/UDP 代理能力。实际排查 Socket 转发问题时,应将 Nginx、操作系统防火墙、云安全组、网络链路以及后端 Socket 服务作为一个完整链路进行分析,才能准确定位问题。