网站访问量并不算大,Nginx、数据库或 Java 服务却突然报 Too many open files,重启后又恢复正常。这个错误很容易让人以为把 ulimit 调大就结束了,结果运行一段时间再次出现。
Linux 里的 file 指磁盘文件,也包括网络套接字、管道和设备。一个连接就可能占用文件描述符,所以这类问题既可能是限制太低,也可能是连接泄漏或程序没有释放资源。

这篇文章会从当前限制、实际占用、泄漏定位和 systemd 配置入手,讲清 Too many open files 应该怎么排查与调整。
一、先分清EMFILE和ENFILE
应用日志里的 Too many open files 多数对应 EMFILE,表示单个进程达到自己的文件描述符上限。ENFILE 则是系统范围的打开文件总数达到限制,两者的处理位置不同。
先查看当前 Shell 的软限制和硬限制:
ulimit -Sn
ulimit -Hn
但这里看到的是当前登录会话,不一定等于 Nginx、MySQL 或 systemd 服务真正使用的限制。只在 SSH 终端执行 ulimit -n,不能代表后台服务已经生效。
二、查看服务真正的文件句柄限制
先找到服务 PID:
systemctl show --property MainPID nginx
再读取该进程的限制:
cat /proc/PID/limits | grep -i 'open files'
也可以直接查看 systemd 单元的设置:
systemctl show nginx -p LimitNOFILE
把 nginx 换成实际服务名。若应用由 Docker 启动,除了宿主机设置,还要检查容器的 ulimits 配置和容器内进程限制。
三、确认进程到底打开了多少文件
使用 lsof 可以统计某个 PID 当前打开的描述符数量:
sudo lsof -p PID | wc -l
更直接的方法是统计 /proc:
ls /proc/PID/fd | wc -l
然后查看它们主要是什么:
sudo lsof -p PID | awk '{print $5}' | sort | uniq -c | sort -nr | head
大量 IPv4 或 IPv6 通常与网络连接有关,大量普通文件可能来自日志、缓存或程序反复打开文件。删除过但仍被进程占用的大文件,也能通过 lsof +L1 找到。
四、限制太低还是程序泄漏
| 现象 | 更可能的判断 | 处理方向 |
|---|---|---|
| 占用随并发升高,回落后释放 | 正常业务需要 | 合理提高限制 |
| 占用只升不降 | 程序或连接泄漏 | 查代码、连接池和日志 |
| 固定时间突然增加 | 定时任务或批处理 | 查任务与任务日志 |
| 多个服务同时报错 | 系统级资源紧张 | 查系统总量与内核限制 |
判断是否泄漏,最好每隔一段时间记录一次 /proc/PID/fd 数量,并和并发连接、请求量对比。访问已经回落,文件描述符仍持续增长,就不能只靠提高上限掩盖。
常见原因包括数据库连接未关闭、HTTP 客户端连接池配置错误、日志文件频繁打开不释放、WebSocket 长连接堆积,以及程序异常重试。上限调得越大,泄漏进程可能拖得越久,最终占用更多内存和连接。
五、systemd服务应该怎么调整
不要直接修改发行版自带的服务文件,升级软件后可能被覆盖。用 override 配置更稳妥:
sudo systemctl edit nginx
加入:
[Service]
LimitNOFILE=65535
保存后重新加载并重启服务:
sudo systemctl daemon-reload
sudo systemctl restart nginx
systemctl show nginx -p LimitNOFILE
65535 只是常见示例,不是所有服务都必须设置成这个值。数据库、代理和高并发服务应结合官方建议、内存、连接数与应用自身参数决定。
六、limits.conf为什么有时不生效
/etc/security/limits.conf 主要由 PAM 登录会话应用,对 SSH 用户启动的进程有效,但 systemd 管理的系统服务经常不读取这里的值。因此修改后登录会话发生变化,后台服务却仍保持原限制。
用户级程序可以在 limits.conf 或 /etc/security/limits.d/*.conf 中设置 nofile,随后重新登录验证。系统服务则优先使用 LimitNOFILE。容器还要在 Compose 或运行参数中单独配置。
系统范围还存在 /proc/sys/fs/file-max。可用 cat /proc/sys/fs/file-nr 查看已分配和上限,但除非确认遇到 ENFILE,不建议一上来就修改系统总上限。
七、调整之后还要看应用参数
提高操作系统限制后,Nginx 的 worker_rlimit_nofile、数据库最大连接数、反向代理连接池和应用线程数仍可能限制实际并发。各层参数要互相匹配。
例如把文件句柄提高到很大,却让数据库允许远超内存承受能力的连接,可能从句柄不足变成内存耗尽。配置调整前先保留原值,并观察连接数、内存和响应时间。
当现有 VPS 的连接规模确实长期增长时,可以再评估升级内存和网络。选择支持后续扩容的平台,如 萤光云 和 **LightNode**,前期用监控确认瓶颈后再加配置,比看到一次报错就换高配更稳。
八、我的处理建议
我会先从错误发生的进程入手,核对 /proc/PID/limits 和当前 fd 数量,再观察占用是否随业务回落。确认只是正常并发超过旧限制,才调整 systemd 和应用参数。
**限制值是容量边界,不是故障修复按钮。**文件描述符持续泄漏、连接没有超时或任务反复启动时,必须处理程序和配置本身,否则更大的上限只会推迟故障。
常见问题
问:重启服务后Too many open files消失,算解决了吗?
答:不算。重启会释放描述符,还需要判断限制过低还是持续泄漏。
问:ulimit -n改成65535后为什么服务没变化?
答:Shell 设置不一定影响 systemd 服务,应查看并设置对应单元的 LimitNOFILE。
问:文件描述符只代表磁盘文件吗?
答:不是,网络套接字、管道和设备也会占用文件描述符。
问:LimitNOFILE可以直接设为无限吗?
答:不建议盲目设置,应根据服务需求、系统资源和应用配置确定合理上限。
问:Docker容器需要单独设置吗?
答:需要时应在容器运行参数或 Compose 配置中设置并在容器内验证。
温馨提示
看到 Too many open files 时,先记录服务 PID、当前句柄数量和限制,再决定是否重启。否则重启后现场消失,最有价值的排查线索也会一起丢失。
真正稳妥的处理是让 系统限制、systemd 配置、应用连接数和服务器资源相互匹配,并持续观察句柄是否正常释放,而不是把所有机器统一套用一个很大的数字。


