本质是nginx未及时调用close()关闭已收fin的连接,导致close_wait堆积;关键在检查client_header_timeout、client_body_timeout和keepalive_timeout是否缺失或过大,并排查后端响应阻塞、lua异常或流式处理模块导致的清理逻辑跳过。

客户端主动断开长连接后,Nginx 未及时回收资源(如 socket 残留、CLOSE_WAIT 积压、worker 连接数异常增长),本质是应用层未能及时感知并关闭对应连接。这不是 epoll 的缺陷,而是 Nginx 在收到 FIN 后,因响应延迟、超时设置不当或逻辑阻塞,未及时调用 close()。
重点看 CLOSE_WAIT 是否堆积
ss -tan state close-wait | wc -l 是第一判断命令。若数值持续高于几十(尤其在低并发场景下),说明 Nginx worker 正在“卡住”本该关闭的连接。此时不是连接没断,而是 Nginx 还没动手关。
- CLOSE_WAIT 状态表示:对端(客户端)已发 FIN,本端(Nginx)收到了,但 Nginx 尚未调用
close() - epoll 不会自动关它,只通知“可读”(读到 0 字节),是否关、何时关,全由 Nginx 应用逻辑决定
检查三类关键超时是否过长或缺失
Nginx 需靠超时机制触发清理动作,尤其当客户端静默断连(如手机休眠、网络中断)时:
-
client_header_timeout和client_body_timeout:控制请求头/体接收阶段,建议设为 15–30 秒 -
keepalive_timeout:客户端空闲连接保持时间,设 15–30 秒较稳妥;超过此值未活动,Nginx 主动发 FIN -
proxy_read_timeout:影响 upstream 连接行为,但若用于 WebSocket 或长轮询,也需同步检查其对 client-side 的间接影响
缺少任一超时,或设为 0/过大(如 300 秒),都可能导致连接挂起数分钟不释放。
确认是否被阻塞在响应处理环节
常见阻塞点包括:
- 后端响应缓慢,Nginx 正在等
proxy_pass返回,无法进入后续 close 流程 - 使用了
body_filter_by_lua或log_by_lua且存在异常或未加pcall包裹,导致 cleanup 逻辑跳过 - 启用了
gzip或sub_filter等流式处理模块,响应体未完整写出前,连接不会关闭
可通过开启 error_log /var/log/nginx/error.log debug;(临时启用)观察日志中是否有 *NNN recv() failed (104: Connection reset by peer) 后长时间无 close() 跟踪。
验证 TCP keepalive 是否启用(辅助探测僵死连接)
对客户端连接本身,Nginx 不直接控制 SO_KEEPALIVE(那是内核 socket 层),但可通过以下方式增强感知:
- 确保系统级参数合理:
net.ipv4.tcp_keepalive_time = 600(首次探测前空闲时间)net.ipv4.tcp_keepalive_intvl = 60(重试间隔)net.ipv4.tcp_keepalive_probes = 3(最大探测次数) - 若使用
proxy_socket_keepalive on;(仅作用于 upstream 连接),它不解决 client → Nginx 段,但能防止上游静默断连干扰判断
快速定位残留连接归属
执行:ss -tanop state close-wait | grep nginx
查看输出中的 ino(inode 号)和 uid,结合 /proc/*/fd/ 反查具体 worker 进程;再比对 ps aux | grep nginx 中各 worker 的 PID,确认是哪个 worker 持有大量 CLOSE_WAIT —— 常指向该 worker 正在处理慢响应或 Lua 异常路径。
不复杂但容易忽略











