“too many open files”错误源于文件描述符三层限制未对齐:进程级(如systemd的limitnofile)、用户级(/etc/security/limits.conf)、内核级(fs.file-max),缺一不可;ulimit -sn/-hn仅反映当前shell继承的软硬限制,实际受三者共同约束。

“Too many open files”错误不是服务写得有问题,而是系统没给你开够口子——文件描述符限制必须分三层调:进程级、用户级、内核级,缺一层都可能白配。
ulimit -Sn 和 ulimit -Hn 看到的只是冰山一角
执行 ulimit -Sn 和 ulimit -Hn 只能查当前 shell 会话的软硬限制,它受三个层级共同约束:
- 当前用户的
/etc/security/limits.conf配置(登录时由 PAM 加载) - 启动该进程的父进程(比如 systemd 或 sshd)是否继承了更高限制
- 内核总上限
fs.file-max是否足够——哪怕单个用户设成 100 万,fs.file-max只有 20 万,照样卡死
常见误区是只改了 limits.conf 就以为万事大吉,结果 systemctl restart nginx 后 cat /proc/$(pidof nginx)/limits | grep "Max open files" 还显示 4096。这是因为 nginx 由 systemd 启动,而 systemd 默认不读 limits.conf。
systemd 服务必须显式加 LimitNOFILE
对 nginx、redis、mysql 这类由 systemd 管理的服务,/etc/security/limits.conf 基本无效。必须在服务单元中直接声明:
- 用
sudo systemctl edit nginx.service创建覆盖配置(推荐,避免升级覆盖) - 在编辑器中写入:
[Service] LimitNOFILE=65535 LimitNPROC=65535
- 保存后执行
sudo systemctl daemon-reload && sudo systemctl restart nginx - 验证:运行
systemctl show -p LimitNOFILE nginx.service,确认输出为LimitNOFILE=65535
注意:如果服务用了多个 worker 进程(如 Node.js cluster),主进程的 LimitNOFILE 会继承给子进程,但子进程自己不能突破这个值——所以设主进程就够了,不用单独配子进程。
fs.file-max 不是“越大越好”,要按物理内存算
fs.file-max 是内核可分配的文件描述符总数,它占用内存。估算公式是:fs.file-max ≈ RAM(GB) × 256000。比如 32GB 内存服务器,设到 800 万就接近安全上限;设到 2000 万可能触发 OOM killer。
- 临时调高:
sudo sysctl -w fs.file-max=8388608 - 永久生效:往
/etc/sysctl.d/99-file-max.conf里写fs.file-max = 8388608,再跑sudo sysctl -p /etc/sysctl.d/99-file-max.conf - 别直接改
/etc/sysctl.conf——现代发行版(Ubuntu 22.04+/CentOS 8+)优先读/etc/sysctl.d/*.conf
改完立刻检查:cat /proc/sys/fs/file-max。如果值没变,大概率是 sysctl -p 没指定正确路径,或文件权限不对(必须是 root:root 且 644)。
limits.conf 生效前最容易漏掉的两件事
/etc/security/limits.conf 配完不生效?八成栽在这两个地方:
- PAM limits 模块没启用:检查
/etc/pam.d/common-session是否含session required pam_limits.so;没有就追加一行 - 没真正“重新登录”:SSH 重连不算,得彻底退出终端、关掉所有 shell,再新开一个;或者用
su - $USER切换一次
验证是否生效:新开终端后,先运行 ulimit -n,再 sudo -u www-data bash -c 'ulimit -n'(换成你的服务用户)。两个值都得是你设的数,才算真正落地。
最常被忽略的是:即使所有配置都对,某些旧版 glibc 或容器环境(如 Docker 默认)仍会把 nofile 锁死在 1024。这时候得看具体运行上下文——比如 Kubernetes Pod 的 securityContext 也要同步配 limits,否则宿主机再怎么调都没用。











