发布时间:2026-09-23 12:30 更新时间:2026-09-23 12:30 阅读量:0
有站长遇到过这样的场景:Nginx 作为反向代理跑了一段时间,突然报错 connect() failed (99: Cannot assign requested address),前端用户访问大面积超时,但后端服务本身看起来没挂。登录机器一看,ss -s 显示大量 TIME_WAIT,本地端口被短时间占满。这时候很多人第一反应是去调 Nginx 的 keepalive_timeout,把它从 65 秒加到 300 秒,结果情况没好转,甚至更糟。问题的关键在于:客户端侧的长连接和后端侧的长连接是两套完全独立的参数,作用范围不重叠,调错了方向自然没用。下面把这两侧拆开讲清楚,再给出连接数打满时的定位顺序。
先建立一个清晰的心理模型。一次用户请求经过的链路是:浏览器 → Nginx(客户端侧)→ 后端应用(后端侧)。这两段连接由不同指令控制。
客户端侧对应的是 keepalive_timeout、keepalive_requests 这类指令,它们写在 http、server 或 location 块里,控制的是 Nginx 与浏览器之间那条连接空闲多久后关闭。它影响的是用户侧连接的复用率,对 Nginx 主动向后端发起连接的行为没有任何影响。
后端侧对应的是 upstream 块里的 keepalive 指令,配合 proxy_http_version 1.1 和 proxy_set_header Connection "" 使用。它控制的是 Nginx 作为客户端,与后端服务器之间维持多少条空闲长连接用于复用。注意这个 keepalive 的值是连接数上限,不是时间,这是新手最容易搞混的一点。
所以当出现本地端口耗尽、Cannot assign requested address 时,矛头指向的是后端侧连接没有复用——Nginx 每转发一个请求就新建一条到后端的 TCP 连接,关掉后进入 TIME_WAIT,本地临时端口(默认范围约 32768-60999)被快速消耗。此时调大 keepalive_timeout 只会让浏览器连接占得更久,跟端口耗尽毫不相干。
光看 Nginx 错误日志不够,要用 ss 把两侧连接分开数。假设后端监听 127.0.0.1:8080,Nginx 本地回源端口随机,可以这样统计:
# 全机 TCP 状态汇总,TIME_WAIT 数量一眼可见
ss -s
统计本机到后端 8080 的连接状态(后端侧)
ss -tan state established '( dport = :8080 )' | wc -l
ss -tan state time-wait '( dport = :8080 )' | wc -l
统计 Nginx 监听端口 80/443 上的连接(客户端侧)
ss -tan state established '( sport = :80 or sport = :443 )' | wc -l如果 dport=:8080 的 time-wait 持续堆积、established 数量却很少,说明回源连接根本没被复用。正常情况下,配置了 upstream keepalive 之后,Nginx 与后端之间应该稳定维持若干条 established 长连接,time-wait 数量保持在低位。
接着用 curl 做一次复用验证,观察 HTTP 版本和连接头。注意要模拟 Nginx 的行为,即 HTTP/1.1 且不带 Connection: close:
# 连续两次请求,看是否复用同一条连接、是否返回 HTTP/1.1
curl -sv -o /dev/null http://127.0.0.1:8080/health 2>&1 | grep -E 'Connected|Connection|HTTP/1'
curl -sv -o /dev/null http://127.0.0.1:8080/health 2>&1 | grep -E 'Connected|Connection|HTTP/1'如果后端返回 HTTP/1.0,或者响应里带 Connection: close,那 Nginx 就没法复用它,每请求必新建连接。这个结果直接解释了端口耗尽的来源。注意不同后端框架的输出细节不一样,以实际环境抓到的响应为准。
后端侧要真正复用连接,三个条件缺一不可:协议升到 HTTP/1.1、清空 Connection 头、upstream 里声明 keepalive 连接池。参考配置如下:
upstream app_backend {
server 127.0.0.1:8080;
keepalive 64; # 空闲长连接池上限,不是超时时间
keepalive_timeout 60s; # 连接空闲多久后关闭,按实际版本支持情况配置
}
server {
listen 80;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
}
}keepalive 的取值可以按后端单机能承受的并发来估,一般设成几十到几百之间,设太大反而让后端连接池膨胀,具体以官方文档和压测结果为准。改完记得 nginx -t 检查再 reload,别直接 restart。
连接数打满时,建议按这个顺序定位,能少走弯路:
第一步,用 ss -s 确认是 TIME_WAIT 堆积还是 established 打满,两者处理方向不同;第二步,用 ss 过滤 dport 指向后端端口,判断是不是回源连接没有复用;第三步,检查 upstream 是否配了 keepalive、proxy_http_version 是否 1.1、Connection 头是否清空,这三项任何一项缺失都会导致复用失效;第四步,验证后端是否支持 HTTP/1.1 长连接,有些应用默认返回 1.0 或主动关闭连接,需要改后端配置;第五步,确认无误后再考虑内核参数,比如调大 net.ipv4.ip_local_port_range 或开启 tcp_tw_reuse,但这些只是缓解,根治仍然靠连接复用。
顺带提醒一句,tcp_tw_reuse 的开启需要谨慎评估,它改变的是 TIME_WAIT 套接字的复用条件,在 NAT 等场景下可能有副作用,改之前先查官方文档和内核版本说明。
把这条链路记住:keepalive_timeout 管浏览器到 Nginx,upstream keepalive 管 Nginx 到后端,端口耗尽几乎都出在后半段。排查时先用 ss 把两侧连接分开数,再用 curl 验证后端是否支持 1.1 长连接,最后才回头看内核参数。养成"先定位是哪一段、再动手改配置"的习惯,比盲目调大超时值有效得多。下一步可以给回源连接加个监控,定期统计 dport 指向后端端口的 time-wait 数量,一旦异常上升就能提前发现复用失效。
| 📑 | 📅 |
|---|---|
| Linux 服务器 CPU 软中断 si 偏高排查:网卡多队列、RPS 与内核参数调整 | 2026-09-23 |
| Docker 容器启动即退出排查:exit code、logs 与前台进程 | 2026-09-23 |
| MySQL单表过亿后的分页优化:延迟关联与覆盖索引 | 2026-09-23 |
| Nginx 反代下 Cookie 域与 Path 错乱排查实战 | 2026-09-23 |
| Linux 改完 fstab 重启起不来:UUID 混用与救援恢复 | 2026-09-22 |
| Linux conntrack 表满导致丢包:现场定位与容量评估 | 2026-09-23 |
| rsyslog 与 journald 双写日志重复或丢失排查 | 2026-09-23 |
| PostgreSQL表膨胀与autovacuum不生效排查实战 | 2026-09-23 |
| Redis 主从切换后写入报 READONLY:三种拓扑的误配排查 | 2026-09-23 |
| Linux负载高但CPU空闲:D状态进程与不可中断睡眠排查 | 2026-09-24 |