如何调整 Linux 文件描述符 ulimit 解决 open files

文章导读
遇到 open files 报错时,我一般不会第一时间去改大参数。先花两分钟确认是不是真的到了限制,否则调完可能只是暂时掩盖了别的问题。可以从三个方向同时看:当前 shell 的软限制、目标进程的实际限制、进程当前打开的 fd 数量。这几项对上了,再动手改 ulimit 才有意义。
📋 目录
  1. 先确认是文件描述符耗尽,而不是误报
  2. 临时调整只适合调试,不适合当最终方案
  3. 持久化配置:limits.conf 仍然是基础
  4. systemd 服务要单独设置 LimitNOFILE
  5. 系统级限制和常见边界
A A

遇到 open files 报错时,我一般不会第一时间去改大参数。先花两分钟确认是不是真的到了限制,否则调完可能只是暂时掩盖了别的问题。可以从三个方向同时看:当前 shell 的软限制、目标进程的实际限制、进程当前打开的 fd 数量。这几项对上了,再动手改 ulimit 才有意义。

先确认是文件描述符耗尽,而不是误报

当应用程序报错 'too many open files' 时,先不要急着调大参数。用 ulimit -n 查看当前 shell 的软限制,再用 cat /proc/<pid>/limits 查看目标进程的实际限制,很多情况是服务启动时继承的限额偏低。同时,通过 ls /proc/<pid>/fd | wc -l 统计该进程当前打开的 fd 数量,若数量接近限制值,则基本可以确认是文件描述符耗尽。注意,某些框架会额外封装错误信息,但底层错误码多为 EMFILE。若确认后,再考虑调整 ulimit。

这里有个细节值得留意:进程的实际限制不一定等于你在配置文件里写的值。比如你用 systemd 启动服务,却只改了 /etc/security/limits.conf,systemd 可能根本不读那份文件。所以用 /proc/<pid>/limits 看到的值,才是这个进程真正生效的软硬限制。如果 fd 数量明明离限制还远,报错却一直出现,那就要检查是不是程序内部对 fd 的封装逻辑有问题,比如某些连接池或事件循环把 fd 状态搞乱了。

临时调整只适合调试,不适合当最终方案

确认是 fd 耗尽后,如果只是想快速验证,可以临时把限制调大。临时调高文件描述符限制,可以在当前终端执行 ulimit -n 65536,但这一设置只对当前 shell 及其子进程有效,终端关闭即失效。注意 ulimit -n 修改的是软限制,若软限制超过硬限制会提示权限不足,可先执行 ulimit -Hn 查看硬限制。若要同时调整软硬限制,可写为 ulimit -SHn 65536。对于正在运行的进程,无法通过 ulimit 命令直接改变其限额,只能重启进程并确保启动环境里设置了正确的 ulimit。

如何调整 Linux 文件描述符 ulimit 解决 open files

这种做法的用途,通常是临时给某个脚本或命令行程序放行,让它能把当前任务跑完。但线上服务不会一直挂在终端里,所以临时调整只能帮你验证“调大之后是不是真的不再报错”,不能代替持久化配置。另外,如果硬限制本身很小,比如默认 1024,普通用户想直接调成 65536 会失败,需要先看硬限制,或者让 root 把硬限制也放宽。

持久化配置:limits.conf 仍然是基础

要永久修改用户的文件描述符限制,可编辑 /etc/security/limits.conf,添加类似行:* soft nofile 65536 和 * hard nofile 65536,其中 * 表示所有用户,也可指定具体用户名或组,如 @admin。注意,该文件对登录会话生效,但已经登录的用户需要重新登录。同时,需要确认 /etc/pam.d/ 下的模块如 pam_limits.so 没有禁用。某些系统上,/etc/security/limits.d/ 目录下的优先级更高,若存在冲突,以 limits.d 为准。

改完 limits.conf 后,不要只打开一个新终端就以为万事大吉。建议用 su - 切换到目标用户,再执行 ulimit -n 看返回值。如果显示的还是旧值,去检查 /etc/security/limits.d/ 下有没有覆盖配置,有些发行版会在那个目录里放一个 90-nproc.conf 之类的文件,里面可能重新定义了 nofile。另外,SSH 服务本身的 UsePAM 如果被设为 no,limits.conf 也不会对 SSH 登录会话生效。

如何调整 Linux 文件描述符 ulimit 解决 open files

systemd 服务要单独设置 LimitNOFILE

如果程序由 systemd 管理,仅修改 limits.conf 往往不生效,因为 systemd 会独立设置进程限制。需要在服务的 service 单元文件中加入 LimitNOFILE=65536,然后执行 systemctl daemon-reload 并重启服务。另外,对于支持 systemd 的容器环境,还需要检查容器自身是否有限制。若服务是用户级 systemd 单元,也需在 user.conf 中设置。如果不确定服务是否通过 systemd 启动,可以查看进程的父子关系或使用 systemctl status。

写 LimitNOFILE 时,可以直接写成 LimitNOFILE=65536,也可以写成 LimitNOFILE=soft:hard 的形式,比如 65536:1048576。但要注意,这个值同样受内核参数 fs.nr_open 限制,如果设得比 nr_open 还大,服务会启动失败。改完 systemd 单元后,用 systemctl status 确认主进程的 PID,再看 /proc/<pid>/limits,这样能确切知道新限制是否生效。

如何调整 Linux 文件描述符 ulimit 解决 open files

系统级限制和常见边界

除了进程级 ulimit,Linux 内核还维护了系统全局的文件描述符限制,可通过 /proc/sys/fs/file-max 查看。如果进程级限制已经很大,但仍然出现 file-max 不足,则需要调整内核参数。临时修改使用 sysctl -w fs.file-max=100000,永久修改则写入 /etc/sysctl.conf。另外,/proc/sys/fs/nr_open 是单个进程可分配 fd 的上限,若配置文件中的值超过 nr_open,内核会报错,注意先检查这个上限。

调整 ulimit 时容易忽略软硬限制的关系。硬限制是内核允许的上限,只有 root 才能调高,普通用户只能在硬限制范围内调大软限制。有些教程直接写 ulimit -n 65535,若硬限制为 1024 会失败。另一个常见坑是 limits.conf 中的语法,比如行内用空格分隔,且注意不要写成 nofile 65535 和 nofile 65535 重复。还有,对于嵌套的 shell 或脚本,如果脚本内部重新设置了 ulimit,外部配置就会被覆盖。排查时建议用 pstree 确认启动路径。

实际处理时,我习惯按这个顺序走:先看 /proc/<pid>/limits 确认当前限制,再看 /proc/<pid>/fd 数量确认是否接近上限;如果服务是 systemd 管,直接改 service 文件并重启;如果是普通用户进程,改 limits.conf 并让用户重新登录;最后用 sysctl 检查 fs.file-max 和 fs.nr_open 是否够大。每改完一步,都重新读一遍进程的 limits 文件,别只看配置写对了没有。这样排查下来,大部分 open files 问题都能定位到具体哪一层限制没放开。