"Too Many Open Files" 是 Linux 运维里的高频报错。它出现的时候,服务往往已经开始不正常:新连接建立不了、日志写不进去、接口返回 500,日志里一行行写着 Too many open files 或 unable to open file,而服务器 CPU、内存看着都还宽裕。新手容易懵:进程数没几个,内存也没满,怎么就"文件太多"了?
关键在于理解"文件句柄(file descriptor,简称 fd)"这个概念的覆盖面。Linux 里"文件"是广义的——不光指磁盘上的文件,还包括 socket 连接、管道、设备、epoll 句柄等。所以文件句柄耗尽,往往是"打开的文件 + 网络连接"总数超过了进程或系统的上限。这篇文章讲清楚:这个上限在哪里设、怎么逐层查看、怎么排查是哪个进程占满了、以及怎么临时和永久地调整。读完之后,你应该能在 5 分钟内定位到是"哪个进程、被哪类 fd 撑爆了",并且知道该在哪一层调参数。
一、先理解什么是文件句柄
Linux 遵循"一切皆文件"的设计。进程每次打开文件、建立网络连接、创建管道,内核都会返回一个整数编号,这就是文件描述符(fd)。进程通过 fd 来操作对应的资源。
几个基础规则:
- 每个进程有独立的 fd 编号空间,从 0 开始。0、1、2 分别是标准输入、标准输出、标准错误,几乎是固定占用的。
- 每个进程有 fd 数量上限(软限制 soft limit、硬限制 hard limit)。
- 整个系统有 fd 总量上限(
fs.file-max)。 - 单个进程能分配的 fd 数量还受内核
fs.nr_open约束。
"Too Many Open Files" 就是某次 open()/socket() 调用时,进程已经用满了自己的 fd 配额,内核拒绝分配新 fd。报错通常长这样:
accept() failed (24: Too many open files)
open() "/var/log/app.log" failed (24: Too many open files)
注意那个 24,它是 errno 的编号,EMFILE=24,看到 24 基本就是 fd 耗尽。如果报的是 ENFILE=23,则是系统级 fd 也耗尽了。
二、上限在哪里设:三层结构
fd 限制分三层,从宽到严,排查和调整时都要照顾到:
| 层级 | 参数/文件 | 作用范围 | 说明 |
|---|---|---|---|
| 内核全局 | fs.file-max |
整个系统 | 系统能打开的最大文件数 |
| 内核每进程硬顶 | fs.nr_open |
单进程 | 任何进程 fd 的绝对上限 |
| 进程限制 | ulimit -n |
当前 shell 及子进程 | 软限制(soft)/硬限制(hard) |
| 用户级持久 | /etc/security/limits.conf 或 limits.d/*.conf |
指定用户/组 | 登录会话生效 |
| systemd 服务级 | LimitNOFILE |
单个服务 | 服务实际生效值 |
| 旧 init 兼容 | /etc/init.d、/etc/systemd/system.conf 的 DefaultLimitNOFILE |
全局默认 | systemd 默认限制 |
很多人改了 limits.conf 却"不生效",99% 是因为服务是 systemd 管理的,systemd 启动的服务不会读 limits.conf,必须用 LimitNOFILE(见第四节)。
三、查看当前限制和用量
排查前先看清现状。
# 1. 当前 shell 的软/硬限制
ulimit -n # 软限制
ulimit -Hn # 硬限制
ulimit -Sn
# 2. 系统级
cat /proc/sys/fs/file-max # 系统总上限
cat /proc/sys/fs/nr_open # 单进程硬顶
cat /proc/sys/fs/file-nr # 已分配/空闲/上限(三个数)
# 3. 某个进程的实际限制(最关键,服务以它为准)
cat /proc//limits | grep -i 'open files'
# 输出示例:
# Max open files 1024 4096 files
# 4. 某个进程当前打开了多少 fd
ls /proc//fd | wc -l
sudo lsof -p | wc -l
# 5. 整个系统当前打开了多少 fd
lsof | wc -l
cat /proc/sys/fs/file-nr
# 6. 找出 fd 最多的进程(快速定位元凶)
for p in /proc/[0-9]*; do
pid=${p#/proc/}
n=$(ls $p/fd 2>/dev/null | wc -l)
[ "$n" -gt 100 ] && echo "$n $pid $(tr -d '\0' < $p/comm 2>/dev/null)"
done | sort -nr | head -20
/proc/ 里 Max open files 那一行最有价值——它显示该进程实际生效的软限制和硬限制。如果你在 limits.conf 里改成了 65535,但这里还显示 1024,说明服务没读到你的配置。
四、按 systemd 服务调整(最重要)
现代 Linux 的服务大多由 systemd 管理。systemd 有自己的一套限制机制,不读 limits.conf。给服务调大 fd 限制要用 override。
# 给某个服务(如 nginx)创建 override
sudo systemctl edit nginx
# 这会创建 /etc/systemd/system/nginx.service.d/override.conf
在打开的编辑器里写入:
[Service]
LimitNOFILE=65535
保存后:
sudo systemctl daemon-reload
sudo systemctl restart nginx
# 验证
cat /proc/$(pidof nginx | awk '{print $1}')/limits | grep -i 'open files'
systemctl show nginx -p LimitNOFILE
注意几点:
LimitNOFILE的值要 ≤fs.nr_open,否则设置无效或服务起不来。- 有些服务(如老版本 Tomcat、PHP-FPM)需要在它自己的配置文件里也设置 limit(如 PHP-FPM 的
rlimit_files),只改 systemd 不够。 - 部分服务有多个进程(master/worker),
pidof会返回多个,取 master 或逐个确认。 - 修改前先确认服务可重启;
daemon-reload本身不影响运行中的服务,但restart会。
如果要在 systemd 层面设全局默认:
# /etc/systemd/system.conf
DefaultLimitNOFILE=65535
# 用户级默认
# /etc/systemd/user.conf
# DefaultLimitNOFILE=65535
sudo systemctl daemon-reexec # 让默认值对后续启动的服务生效(风险:会重新执行 systemd,一般安全)
五、按用户/登录会话调整
非 systemd 管理的场景(直接 shell 启动、老 init、cron),用 limits 配置。
# RHEL/CentOS/Rocky 及其它:/etc/security/limits.conf
# 或在 /etc/security/limits.d/ 下新建 conf(推荐,避免改动主文件)
sudo tee /etc/security/limits.d/99-nofile.conf >/dev/null <<'EOF'
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535
EOF
说明:
- 第一列用户(
*表示所有用户,也可写用户名或@组名),第二列是soft/hard,第三列nofile,第四列数值。 *通常不包含 root(取决于 PAM 配置),所以 root 要单独写。- 只在新登录会话生效。已经打开的 shell 要重新登录,服务要重启。
- 系统需要有
pam_limits.so(一般在/etc/pam.d/login、/etc/pam.d/sshd、/etc/pam.d/su里启用)。多数发行版默认已启用,若没生效要检查这些文件。
验证(新开一个 shell):
ulimit -n # 应显示 65535
六、内核级调整
如果需要更高的总量,调整内核参数。
# 查看当前值
sysctl fs.file-max fs.nr_open
# 临时生效(重启失效)
sudo sysctl -w fs.file-max=2097152
sudo sysctl -w fs.nr_open=1048576
# 永久生效
echo 'fs.file-max = 2097152' | sudo tee -a /etc/sysctl.d/99-file-max.conf
echo 'fs.nr_open = 1048576' | sudo tee -a /etc/sysctl.d/99-file-max.conf
sudo sysctl --system # 重新加载
# 或写 /etc/sysctl.conf(老位置)
风险提示:fs.nr_open 是单进程上限,设置过高本身不危险,但配合应用实际需要就行,别盲目设成天文数字——每个 fd 都对应内核内存,真正的瓶颈通常不在 file-max,而在应用自身的 fd 管理(泄漏)。改内核参数前记录原值,便于回滚。
七、排查:到底是谁占满了 fd
找到"哪个进程"只是第一步,还要知道它把 fd 花在哪了。
# 看某进程打开的 fd 都指向什么
sudo ls -l /proc//fd | head -50
# 统计 fd 类型分布
sudo ls -l /proc//fd | awk '{print $NF}' | sed 's/[0-9]*$//' | sort | uniq -c | sort -nr | head
# 用 lsof 分类
sudo lsof -p | awk '{print $5}' | sort | uniq -c | sort -nr # 按类型统计
sudo lsof -p | grep -i socket | wc -l # socket 数量
sudo lsof -p | grep -i 'REG' | wc -l # 普通文件数量
# 看是否有大量 TIME_WAIT / CLOSE_WAIT 连接(socket 撑爆的典型)
ss -s
ss -tan | awk '{print $1}' | sort | uniq -c | sort -nr | head
几个高频场景:
- 大量
socket且状态是CLOSE_WAIT:应用收到对端关闭但自己没 close,连接泄漏。要修代码,及时关闭连接。 - 大量普通文件(REG):打开了文件没关闭,典型的文件句柄泄漏,或日志文件被反复打开。
- 大量
pipe:进程间通信管道没释放。 - fd 指向
(deleted):文件被删了但进程还持有句柄,磁盘空间也不释放(df满但du不大就是这个)。
# 找被删除但仍被占用的文件(磁盘空间不释放)
sudo lsof | grep deleted | head
# 或
sudo lsof +L1
排查时有一个容易被忽略的细节:fd 数量并不等于"连接数"。一个进程可能为每个连接持有多个 fd(比如代理场景里,客户端连接和上游连接是两条 fd),再加上配置文件的 fd、日志的 fd、epoll 事件句柄等。所以看到 ls /proc/ 的结果接近上限时,要结合业务量判断是"正常增长"还是"泄漏"。
判断是不是泄漏,最有效的办法是在业务量稳定的时段连续采样:如果请求量没有增加,fd 数量却持续单调上涨、从不回落,那就是典型的泄漏,必须从代码或连接管理上修。如果 fd 数量随请求量同步涨落(高峰多、低谷少),那属于正常现象,调大限制即可。这两种情况处理方式完全不同,先分清楚再动手,才能避免"调了限制反而让泄漏藏得更深"。
另一个实用技巧是观察 fd 的"年龄"。新开的连接和长期不释放的连接,可以通过 lsof -p 里的时间、ss -tan 的连接状态辅助区分。若大量连接状态停在 ESTABLISHED 且长时间不活跃,多半是应用没有正确设置空闲超时或没有关闭;若停在 CLOSE_WAIT,则是对端已关闭而本端没关。
八、一个真实排障案例
现象:一台 Nginx 反向代理服务器,运行几周后开始间歇性 502,error.log 里大量 accept() failed (24: Too many open files),重启 Nginx 后正常,过一两周又复发。
排查过程:
# 1. 确认 Nginx 的限制
cat /proc/$(cat /run/nginx.pid)/limits | grep -i 'open files'
# 输出:Max open files 1024 4096 files —— 软限制只有 1024!
# 2. 看 Nginx 当前打开了多少 fd
sudo ls /proc/$(cat /run/nginx.pid)/fd | wc -l
# 输出:980+ —— 接近 1024,已经快满了
# 3. 看这些 fd 是什么
sudo ls -l /proc/$(cat /run/nginx.pid)/fd | awk '{print $NF}' | grep -c socket
# 大部分是 socket
# 4. 看连接状态
ss -tan | grep -c CLOSE_WAIT
# 有几百个 CLOSE_WAIT —— 后端处理完没有正确关闭连接
根因有两点:一是 Nginx 的 LimitNOFILE 没设,沿用了默认 1024;二是后端某些长连接没有正确关闭,导致 Nginx 侧累积了大量 CLOSE_WAIT,白白占着 fd。
处理:
# 第一步:临时提高运行中 Nginx 的限制(不等重启,部分场景可用)
# Nginx 支持通过 worker_rlimit_nofile 在主配置里设置(需 reload)
sudo nginx -T | grep worker_rlimit_nofile
# 在 /etc/nginx/nginx.conf 的 main 段加入:
# worker_rlimit_nofile 65535;
# 同时保证 systemd 也放行:
sudo systemctl edit nginx
# [Service]
# LimitNOFILE=65535
# 第二步:让 systemd 生效
sudo systemctl daemon-reload
# 第三步:reload(平滑重载,不中断连接)
sudo nginx -t && sudo nginx -s reload
# 验证
cat /proc/$(cat /run/nginx.pid)/limits | grep -i 'open files'
第四步是修根因:让后端正确关闭连接,避免 CLOSE_WAIT 堆积;同时给 Nginx 配置合理的 keepalive 和上游连接池大小。
结果:调大 fd 限制后,CLOSE_WAIT 也不再堆积(后端也修了),502 不再复发。
这个案例的两个教训:① systemd 服务要在 LimitNOFILE 里调,光改 limits.conf 没用;② 调大限制只是止血,CLOSE_WAIT 堆积才是根因。
九、临时与永久调整对照
| 调整方式 | 命令/文件 | 生效时机 | 适用 |
|---|---|---|---|
| 当前 shell 临时 | ulimit -n 65535 |
立即(仅当前会话) | 调试 |
| 用户会话持久 | /etc/security/limits.d/99-nofile.conf |
重新登录 | 非 systemd 场景 |
| systemd 服务 | systemctl edit + LimitNOFILE |
daemon-reload + 重启服务 |
现代服务的正解 |
| 内核全局 | sysctl fs.file-max |
立即 | 系统总量不足 |
| 单进程硬顶 | sysctl fs.nr_open |
立即 | 需要极高上限 |
常见问题
Q:改了 /etc/security/limits.conf 为什么不生效?
两种可能:一是服务由 systemd 管理,systemd 不读 limits.conf,要用 LimitNOFILE;二是已登录的会话不会重新读取,要重新登录。用 cat /proc/ 看实际生效值最准。
Q:ulimit -n 显示的和我设的不一样?
ulimit -n 是软限制,可能被硬限制或内核 fs.nr_open 截断。ulimit -Hn 看硬限制,cat /proc/sys/fs/nr_open 看内核硬顶,三者取小。
Q:怎么区分是文件 fd 泄漏还是 socket 泄漏?
sudo ls -l /proc/ 看链接目标:指向 socket 的就是连接,指向普通文件的是文件。再用 ss -tan 看连接状态,CLOSE_WAIT 多是连接没关。
Q:调大 LimitNOFILE 后 fd 增长更快了,是不是治标不治本?
是的。调大限制只是提高天花板,如果 fd 是泄漏(只增不减),迟早还会撞顶,而且撞顶前占用更多内存。必须同时查泄漏根因。
Q:df 显示磁盘满,但 du 统计没那么多,和 fd 有关吗?
有关。文件被删除后,若还有进程持有 fd,空间不会释放。用 sudo lsof | grep deleted 找到持有者,重启该进程或让其关闭句柄,空间即释放。
总结
"Too Many Open Files" 是 fd 配额被用尽。Linux 里 fd 涵盖文件、socket、管道等,所以既可能是文件泄漏,也可能是连接泄漏。
fd 限制分三层:内核(fs.file-max、fs.nr_open)、用户会话(/etc/security/limits.d/)、systemd 服务(LimitNOFILE)。现代服务几乎都走 systemd,改 limits.conf 往往无效,要用 systemctl edit 加 LimitNOFILE。
排查用 cat /proc/ 看实际限额,ls /proc/ 看用量,sudo lsof -p 和 ss -tan 看 fd 类型与连接状态。重点是区分文件泄漏、socket 泄漏(尤其 CLOSE_WAIT)和已删除文件占用。
调大限制是止损,修 fd 泄漏才是治本。真实案例里,Nginx 因为软限制只有 1024 且后端连接未正确关闭,导致 CLOSE_WAIT 堆满 fd 而 502——既要调 LimitNOFILE,也要修连接关闭逻辑。
企业QQ咨询




