Nginx 使用 epoll 时不主动回收 CLOSE_WAIT 连接,因 epoll 仅通知可读事件,而 CLOSE_WAIT 的清理依赖应用层调用 close();常见原因包括后端响应慢、客户端异常断连、upstream 主动关闭未及时处理及 Lua 模块遗漏 cleanup;缓解需配置合理超时、启用 TCP keepalive、监控定位并确保快速响应可读事件后关闭连接。

Nginx 使用 epoll 模型时,并不会主动回收处于 CLOSE_WAIT 状态的连接,这是由 TCP 协议机制和应用层行为共同决定的,而非 epoll 本身的限制。
为什么 epoll 不处理 CLOSE_WAIT
epoll 是 I/O 多路复用机制,只负责监听文件描述符上的就绪事件(如可读、可写、错误等)。当对端发送 FIN 后,本端进入 CLOSE_WAIT 状态,此时 TCP 连接在内核中仍存在,socket 可读(读到 0 字节),但 epoll 并不“感知”该状态变化——它只反馈可读事件。是否关闭 socket,完全取决于 Nginx 应用层是否调用 close() 或 shutdown()。
CLOSE_WAIT 的本质是:对端已关闭连接,本端尚未调用 close()。这属于应用层资源释放责任,epoll 不介入连接状态管理。
Nginx 中 CLOSE_WAIT 积压的常见原因
- 后端服务响应慢或未及时关闭连接:Nginx 作为反向代理,若 upstream 返回后未正确结束响应(如未写完响应体、超时未清理),可能导致连接卡在 CLOSE_WAIT
- 客户端异常断连 + Nginx keepalive 配置不当:客户端发 FIN 后崩溃,Nginx 保持长连接但未检测到对端关闭,又未启用合适的 read timeout,导致 socket 滞留
- upstream 主动关闭而 Nginx 未快速响应:例如 upstream 进程重启、连接池强制回收,Nginx 收到 FIN 后因忙于其他请求,延迟处理可读事件,进而延迟 close()
- 自定义模块或 Lua 脚本阻塞或遗漏 cleanup:使用 ngx_lua 时,若在 body_filter 或 log_by_lua 中发生异常且未确保 socket 关闭逻辑执行,易引发 CLOSE_WAIT 泄漏
如何缓解和排查
关键思路是缩短 CLOSE_WAIT 存续时间,推动 Nginx 尽快调用 close():
-
设置合理的超时参数:在
http或location块中配置proxy_read_timeout、proxy_send_timeout、keepalive_timeout,避免连接无限挂起 -
启用 TCP keepalive 检测:通过
proxy_socket_keepalive on(1.15.6+)让 upstream 连接启用 SO_KEEPALIVE,辅助发现僵死连接 -
监控并定位源头:用
ss -tan state close-wait | wc -l实时统计;结合ss -tanop | grep CLOSE-WAIT查看对应 PID 和进程名,确认是 nginx worker 还是上游服务 -
检查日志与错误模式:开启
error_log ... debug(谨慎使用),关注 “recv() failed (104: Connection reset by peer)”、“upstream prematurely closed connection” 类报错,常伴随 CLOSE_WAIT 上升
补充说明:epoll 与连接回收的关系
epoll 本身不回收连接,但它影响 Nginx 处理关闭事件的效率。例如:
- 若 epoll_wait 返回可读事件,Nginx 会尝试读取 socket,读到 0 字节即判定对端关闭,随后触发 cleanup 流程
- 如果该 socket 长期无新事件(比如被遗漏或调度延迟),CLOSE_WAIT 就会持续存在
- Nginx 的 event 模块在收到可读事件后,会调用
ngx_http_set_keepalive()或直接ngx_close_connection(),这才是真正释放连接的动作
所以优化方向不是“让 epoll 回收”,而是确保 Nginx 快速响应可读事件、及时执行关闭逻辑,并从协议交互和超时设计上减少等待窗口。










