nginx单worker进程句柄耗尽是因fd按进程隔离且未释放所致,需通过lsof、/proc/pid/fd/及error.log定位泄漏pid,排查keepalive超时、reuseport缺失、日志缓冲关闭及第三方模块未close等根源。

这个问题很典型:高并发下,部分 worker 进程报 Too many open files,而其他 worker 进程的 CPU 和连接数却很低。这不是负载不均的表象,而是句柄资源被单个进程“锁死”或“泄漏”的信号——根本原因在于 Nginx 的进程模型和文件描述符(fd)分配机制是**按 worker 进程隔离的**,每个 worker 只能使用自己名下的 fd 限额,无法共享。
确认是否真为单进程句柄耗尽
别直接改配置,先定位真实瓶颈:
- 查所有 worker 进程的当前 fd 使用量:
for pid in $(pgrep -f "nginx: worker"); do echo "PID $pid: $(ls /proc/$pid/fd/ 2>/dev/null | wc -l) fds"; done | sort -k4 -n - 对比各进程的硬限制:
cat /proc/$(pgrep -f "nginx: worker" | head -1)/limits | grep "Max open files"(取一个 worker PID 查) - 看 error.log 是否有集中在某几个 PID 的报错,例如:
2026/10/09 12:45:22 [alert] 12345#12345: *98765 accept() failed (24: Too many open files)
检查 worker 进程是否卡在长连接或未释放的 fd 上
句柄不释放 ≠ 配置太小,更可能是连接没正常关闭或日志/上游连接堆积:
- 用
lsof -p PID或ls -l /proc/PID/fd/看该繁忙 worker 打开了哪些 fd:
重点关注大量socket:[...](尤其是状态为TIME_WAIT或无响应的 ESTABLISHED)、重复打开的日志文件、未关闭的 upstream socket(如 proxy_pass 到后端失败后残留) - 检查是否有 keepalive 连接异常堆积:
确认keepalive_timeout和keepalive_requests是否设得过大(例如 timeout=600s + requests=10000),导致连接长期挂起却不复用 - 排查是否启用了未配超时的长轮询或 SSE 接口,客户端断连后服务端未及时检测并关闭 fd
验证事件分发是否失衡(epoll 模式下常见)
Nginx 默认用 epoll,但若系统内核或配置不当,可能导致连接只落到少数 worker:
- 确认
accept_mutex状态:
如果关了(accept_mutex off;),且未启用reuseport,在某些内核版本下会出现惊群或分发倾斜 - 检查是否启用了
reuseport(需 Linux ≥3.9 + Nginx ≥1.9.1):
在events块中加use epoll; reuseport;,可让内核在 listen socket 层就做连接分发,显著缓解单 worker 压力 - 观察
netstat -antp | grep :80 | awk '{print $7}' | cut -d',' -f2 | sort | uniq -c | sort -nr,看连接是否明显集中在少数 PID
排除日志和模块引发的隐性 fd 泄漏
有些看似无关的配置,会在高并发下放大 fd 消耗:
- 检查 access_log 是否开启缓冲(
buffer=64k flush=5s),否则每条日志都是一次 write + fsync,易触发临时 fd 分配 - 禁用不必要的模块,特别是动态加载的第三方模块(如某些 Lua 脚本、geoip2、njs 处理逻辑),它们可能在异常路径中未正确 close fd
- 确认
error_log级别不是debug—— debug 日志会大量打开临时文件和 socket,且难以释放 - 检查是否对每个请求都打开了新文件(如通过
include动态加载配置、或 Lua 中反复io.open()却未close)











