在流量入口网关层,Nginx 默认的出厂配置往往面向通用低负载环境。一个经典的线上事故是:当秒杀或突发流量涌入时,Nginx 机器 CPU 利用率并不高,但错误日志中大量刷出 cannot assign requested address 或 connect() failed (110: Connection timed out) while connecting to upstream。
本文梳理该类故障背后的 TCP 协议栈机制与标准化调优清单。
一、为什么反向代理默认会耗尽本地临时端口
许多团队在配置 Nginx upstream 时只写了后端服务器地址,却忽略了 Nginx 对上游(Upstream)默认使用 HTTP/1.0 短连接。这意味着每转发一个外部请求,Nginx 都要向后端服务发起一次新的 TCP 三次握手,请求结束后立即四次挥手关闭。
由于主动关闭连接的一方会进入长达 2 * MSL(Linux 下固定为 60 秒)的 TIME_WAIT 状态,当每秒转发 1000 个请求时,仅单个上游 IP 就会积累约 60,000 个 TIME_WAIT 连接,直接耗尽 net.ipv4.ip_local_port_range,导致新请求报错 99: Cannot assign requested address。
正确的 Upstream Keepalive 长连接配置
upstream backend_service {
server 10.10.2.11:8080 max_fails=3 fail_timeout=10s;
server 10.10.2.12:8080 max_fails=3 fail_timeout=10s;
# 每个 Worker 进程缓存的最大空闲长连接数
keepalive 256;
# 单个长连接最大复用请求次数
keepalive_requests 10000;
# 空闲长连接保持时长(需略小于后端服务的 idleTimeout,避免后端主动断连引发 502)
keepalive_timeout 55s;
}
server {
listen 80 reuseport backlog=16384;
server_name api.example.com;
location / {
proxy_pass http://backend_service;
# 必须显式指定 HTTP/1.1 并清空 Connection 头,否则默认发送 Connection: close
proxy_http_version 1.1;
proxy_set_header Connection "";
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_connect_timeout 2s;
proxy_read_timeout 10s;
proxy_send_timeout 5s;
}
}二、排查全连接队列(Accept Queue)与半连接队列溢出
当瞬时并发新建连接极高时,可以通过 netstat -s 或 ss -lnt 检查内核连接队列是否发生丢包:
# 查看全连接队列溢出次数
netstat -s | grep -E "listen|SYNs"
# 输出示例:
# 1428 times the listen queue of a socket overflowed
# 1428 SYNs to LISTEN sockets dropped
# 查看具体监听端口的 Recv-Q(当前待 accept 队列长度)与 Send-Q(最大队列上限)
ss -lnt需要注意的是,一个监听套接字的实际全连接队列上限取的是 应用程序 listen(fd, backlog) 参数与内核 net.core.somaxconn 两者的较小值:
[
\text{Actual Backlog} = \min(\text{Application Backlog}, \text{net.core.somaxconn})
]
如果只调大了 sysctl.conf 中的 somaxconn,却没有在 Nginx 的 listen 80 backlog=16384; 中同步放大(Nginx 默认 backlog 仅为 511),全连接队列依然会在 511 处截断。
三、生产网关服务器 /etc/sysctl.conf 推荐基线
# 放大监听套接字全连接队列与半连接队列上限
net.core.somaxconn = 32768
net.ipv4.tcp_max_syn_backlog = 32768
net.core.netdev_max_backlog = 32768
# 扩大本地临时端口可用范围
net.ipv4.ip_local_port_range = 10240 65535
# 允许将处于 TIME_WAIT 状态的 socket 安全复用于新的出向连接(仅对客户端/代理出向连接生效)
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 262144
# 缩短 FIN_WAIT_2 超时时间
net.ipv4.tcp_fin_timeout = 15
# 开启 SYN Cookies 防范突发 SYN Flood 占满半连接队列
net.ipv4.tcp_syncookies = 1切记:Linux 4.12 内核已彻底移除
net.ipv4.tcp_tw_recycle参数。在 NAT 网络环境下开启tcp_tw_recycle会因为时间戳单调性校验导致同一公网出口下的多台客户端设备握手中途静默丢包。