Nginx最初以HTTP反向代理和Web服务器闻名,但自从1.9.0版本引入stream模块之后,它已经具备了完整的四层(传输层)代理能力。所谓四层代理,就是Nginx不解析应用层协议的内容,只根据IP地址和端口号对TCP或UDP流量进行转发。这种方式开销更低、通用性更强,无论是MySQL、Redis、Postfix,还是DNS、MQTT,只要基于TCP或UDP的协议都可以代理。本文将从模块原理、核心配置语法到实战案例,完整讲解Nginx流代理的配置方法。

一、stream模块的原理与启用方式
stream模块与http模块是并列关系,两者都直接位于main配置块之下。这一点非常重要:stream块不能写在http块内部,否则Nginx启动时会直接报错。stream模块工作在传输层,收到客户端连接后,它与后端服务器建立一条新的TCP连接,然后把两条连接之间的数据原样搬运,全程不去解析HTTP头或者任何应用层内容。
默认情况下,官方发布的Nginx并没有编译stream模块,需要手动启用。可以先通过nginx -V命令查看编译参数,确认是否包含--with-stream。如果没有,就需要重新编译:
# 查看当前编译参数 nginx -V 2>&1 | tr ' ' '\n' | grep stream # 编译时启用stream模块,同时启用UDP负载均衡支持 ./configure --with-stream --with-stream_realip_module make && make install
如果使用的是包管理器安装,例如Ubuntu下的nginx-full或者CentOS下的EPEL源Nginx,stream模块通常以动态包形式提供,执行apt install libnginx-mod-stream或yum install nginx-mod-stream即可。动态模块方式下,还需要在nginx.conf顶部加上load_module modules/ngx_stream_module.so;来手动加载。
二、TCP代理的核心配置语法
stream配置的基本结构是一个stream块包含多个server块,每个server块定义监听端口和转发目标。与HTTP代理不同,四层代理使用的是proxy_pass指令(属于stream模块自己的同名指令),后面直接跟后端的IP和端口,不支持URI路径。下面是一个最简单的TCP转发配置:
# nginx.conf 主配置文件
stream {
upstream mysql_backend {
server 192.168.1.10:3306 weight=1 max_fails=3 fail_timeout=30s;
server 192.168.1.11:3306 weight=1 max_fails=3 fail_timeout=30s;
}
server {
listen 3306; # 对外监听的端口
proxy_pass mysql_backend; # 转发到后端MySQL集群
proxy_connect_timeout 5s; # 与后端建立连接的超时时间
proxy_timeout 600s; # 两条连接之间无数据的空闲超时
}
}
这个配置把本机3306端口的流量转发到内网两台MySQL服务器,同时实现了负载均衡。upstream中的weight控制权重,max_fails配合fail_timeout实现后端健康剔除:如果某台后端在30秒内失败3次,就会被暂时摘除。对于MySQL这类长连接服务,proxy_timeout一定要设置得足够大,默认值是10分钟,空闲连接超过这个时间会被Nginx主动断开,应用层经常表现为莫名其妙的连接重置。
另一个常用指令是proxy_next_upstream。当与某台后端建立连接失败时,Nginx可以自动尝试下一台,配置为proxy_next_upstream on;即可。要注意的是,这个机制只在连接阶段生效,一旦连接建立并开始传输数据,中途断开是不会重试的,所以它适合TCP握手失败的场景,不能当作业务层的重试机制使用。
三、UDP转发与会话保持
UDP代理只需在listen指令后加上udp参数。UDP是无连接协议,Nginx通过客户端的源地址和端口来识别一个会话,维护一张会话表。最常见的场景是代理DNS服务:
stream {
resolver 114.114.114.114 valid=300s;
upstream dns_servers {
server 8.8.8.8:53;
server 1.1.1.1:53;
}
server {
listen 53 udp reuseport; # udp表示监听UDP协议
proxy_pass dns_servers;
proxy_responses 1; # 每个请求期望1个响应,收到即结束会话
proxy_timeout 10s; # UDP会话的空闲超时
}
}
proxy_responses是UDP代理的关键参数,表示客户端一个请求对应后端几个响应包。对于普通DNS查询是1,如果转发的是syslog这类单向上报,设置为0表示不等待响应。如果不合理设置这个值,会话表可能膨胀,占用大量内存。reuseport参数让多个worker进程各自绑定套接字,提升UDP接收性能,在高流量场景下建议开启。
会话保持方面,TCP场景可以使用hash $remote_addr consistent;让同一客户端IP始终落到同一台后端,配合一致性哈希算法可以在后端增减时减少会话迁移。对于需要更细粒度控制的场景,还可以通过split_clients模块或者map指令结合自定义变量来做路由。
四、获取客户端真实IP与生产注意事项
四层代理有一个绕不开的问题:后端看到的连接来源是Nginx的IP,而不是客户端真实IP。stream模块提供了PROXY protocol支持来解决这个问题。Nginx作为接收端配置proxy_protocol on;,作为发送端配置proxy_protocol on;转发时带上协议头,后端也必须支持解析PROXY protocol才行,例如HAProxy、PostgreSQL较新版本都支持。配置示例:
server {
listen 3306 proxy_protocol; # 接收带PROXY protocol的连接
proxy_pass mysql_backend;
proxy_protocol on; # 向后端转发时附带真实源IP信息
}
生产环境还有几点需要注意。第一,stream和http可以共存于同一个Nginx实例,但监听端口不能冲突,可以用netstat -tlnpu排查。第二,Nginx的worker_connections要调大,因为每一个四层代理连接会占用两个连接资源,默认的1024远远不够,建议设置到65535以上,同时调整系统的文件描述符限制。第三,排障时开启访问日志非常关键,stream模块有自己的log_format指令,可以记录客户端地址、后端地址、连接时长、收发字节数等信息:
log_format stream_log '$remote_addr [$time_local] $protocol '
'$status $bytes_sent $bytes_received '
'$session_time "$upstream_addr"';
server {
listen 3306;
proxy_pass mysql_backend;
access_log /var/log/nginx/stream.access.log stream_log;
}
通过日志中的session_time和字节数,可以快速判断连接是否被提前掐断、流量是否异常。掌握了stream模块的这些配置要点后,Nginx就不再只是一个Web服务器,而是一个轻量高效的四层负载均衡器,完全可以替代HAProxy完成大部分TCP/UDP转发场景的任务。