答案是反向代理长连接池未正确释放导致的进程级文件描述符泄漏。需检查upstream keepalive配置闭环,确保proxy_http_version 1.1、proxy_set_header Connection ""、proxy_set_header Upgrade $http_upgrade三者同时存在,并验证后端keepalive timeout是否短于Nginx设置。

看到 Nginx 日志里反复出现 “accept() failed (24: Too many open files)”,且错误集中在个别 worker 进程(比如 PID 1234、5678),其他 worker 暂时正常——这大概率不是系统级 fd 不足,而是反向代理长连接池未正确释放引发的缓慢泄漏。
确认是长连接泄漏而非系统总量不足
先排除干扰:检查 dmesg | grep -i "file.*max",若无内核级告警;再用 cat /proc/sys/fs/file-nr 查看已分配 fd 总数,若远低于 file-max 值,基本可锁定为进程级泄漏。重点观察出问题的 worker 进程:
– 用 lsof -p PID | wc -l 对比不同 worker 的打开文件数,泄漏进程通常持续增长;
– 注意其中大量是 socket 类型,且状态为 ESTABLISHED 但无活跃通信;
– 检查对应 upstream 配置是否启用了 keepalive,但未配 proxy_http_version 1.1 或 proxy_set_header Connection ""。
检查 upstream keepalive 配置是否闭环
Nginx 的 upstream keepalive 是“伪长连接”:它维护与后端的空闲连接池,但必须满足三个条件才能复用,否则连接会堆积不释放:
– 后端服务本身支持 HTTP/1.1 keepalive(如 Tomcat、Gunicorn 默认开启);
– Nginx 配置中明确设置 proxy_http_version 1.1 和 proxy_set_header Connection ""(清空 Connection 头,避免被后端误解为关闭);
– upstream 块中启用 keepalive N(如 keepalive 32),且 keepalive_requests 和 keepalive_timeout 不设得过大(建议 100 请求 / 60s 内);
– 若使用了 proxy_buffering off(如 WebSocket 场景),需额外确认 proxy_http_version 和 Connection 头是否同步配置,否则连接无法进入 keepalive 池,直接退化为短连接+fd 泄漏。
验证并收缩泄漏路径
临时缩小范围快速验证:
– 在出问题的 location 块中,加一行 proxy_set_header Connection "close",强制禁用长连接,观察错误是否停止增长;
– 或将 upstream 的 keepalive 改为 0,重启 Nginx,对比 lsof 输出变化;
– 若泄漏消失,说明问题确在 keepalive 配置闭环缺失;
– 回滚后,逐项补全:确保 proxy_http_version 1.1、proxy_set_header Connection ""、proxy_set_header Upgrade $http_upgrade(WebSocket 必须)三者同时存在;
– 对接 Node.js、Spring Boot 等常见后端时,还需确认其 keepalive timeout 是否显著短于 Nginx 的 keepalive_timeout,否则 Nginx 会保留已失效连接。
补充监控与预防手段
光靠重启掩盖不了泄漏:
– 在 nginx.conf 的 events 块中加入 worker_connections 4096;,并配合 worker_rlimit_nofile 设置(如 10240),为排查留出缓冲窗口;
– 使用 ss -s 定期统计 socket 状态,关注 tw(TIME_WAIT)和 estab 数量趋势;
– 在 access_log 中添加 $connection_requests 和 $upstream_connect_time,识别高连接频次或超长空闲连接;
– 对关键 upstream,启用 keepalive_requests 限制(如 100),避免单连接无限复用导致上下文残留。


















