
删掉几 GB 的日志,再看 df -h,磁盘使用率几乎没动。文件明明已经找不到了,空间去了哪里?
常见原因是文件名已经被删除,但程序还打开着这个文件。 在没有其他硬链接的情况下,要等最后一个打开它的文件描述符关闭,文件占用的空间才会释放。[1] 这时继续删除其他日志,未必能解决问题,还可能丢掉排障证据。
本文按“确认文件系统 → 对比占用 → 找到持有进程 → 安全关闭引用 → 验收”的顺序排查。命令面向使用 GNU 工具的常见 Linux 服务器;容器、网络文件系统和不同发行版的输出可能有差异。以下是文档核验后的操作示例,不代表已经在你的服务器上执行。
一、先确认你查的是同一块文件系统
df 看的是文件系统整体空间;du 沿目录统计仍能访问到的文件占用。它们不是同一套统计口径,不能因为结果不同就判定系统出错。[2][3]
假设日志位于 /var/log,先运行这些只读命令:
findmnt -T /var/log
df -hT /var/log
df -i /var/log
sudo du -xhd1 /var/log
其中 df -i 用来区分 inode 不足和容量不足。du -x 不跨到其他文件系统,-d1 只展示一层目录。上面的命令不会清理文件,但扫描目录会产生 I/O;大型服务器建议在业务低峰执行。
注意比较范围。如果 /var/log 只是根文件系统中的一个目录,拿它的 du 总量与整个根分区的 df 比较,差距当然可能很大。需要完整对照时,应扫描 findmnt 显示的挂载点,例如:
# 仅当目标文件系统挂载在 / 时,才采用这个范围
sudo du -xhd1 /
如果 /var 是独立分区,就改为扫描 /var。不要直接套用示例路径。
二、找到链接数为零、仍然打开的文件
在主机上有 lsof 时,先查看:
sudo lsof -nP +L1
+L1 筛选链接数小于 1 的打开文件,适合寻找已经从目录中删除、却还被进程持有的文件。[4] -nP 避免主机名和端口名解析。结果较多时,可在确认 PID 后缩小范围;不要只凭 (deleted) 字样就结束判断。
一条示意输出可能是:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
nginx 2468 root 7w REG 253,0 8589934592 0 1234 /var/log/nginx/access.log (deleted)
读这条记录时,重点看:
PID:哪个进程持有文件。FD:文件描述符;7w表示编号 7,以写方式打开。TYPE:这里是普通文件REG,不是套接字。DEVICE与NODE:用于识别文件所在设备及 inode。NLINK:链接数为零。SIZE/OFF:此例是文件逻辑大小,不应直接当作实际可回收磁盘空间。
同一份日志可能同时被主进程和多个工作进程打开。不要把每一行大小直接相加,否则会重复计算同一个文件。稀疏文件的逻辑大小也可能远高于实际分配空间。
如果命令没有输出,先检查是否有足够权限、是否在正确的主机或容器命名空间中,再考虑其他原因。不要为了解决“磁盘满”就批量杀进程。
三、确认持有文件的进程与服务
用上一步查到的真实 PID 检查进程,例如:
# 2468 只是示例,执行前替换为实际 PID
ps -fp 2468
sudo readlink /proc/2468/fd/7
sudo stat -L /proc/2468/fd/7
FD 栏里的 7w 对应路径末尾的 7,不要把字母一起写进去。进程可能已退出,描述符也可能重新分配,所以每次操作前都应重新确认。不要对进程参数、环境变量或日志内容截图后公开分享,它们可能包含凭证和用户数据。
处理方法取决于文件属于什么业务:
| 文件和进程类型 | 优先处理方向 |
|---|---|
| 支持日志重新打开的 Web 服务 | 使用该服务的日志重开机制 |
| 不支持重开的普通应用 | 在维护窗口正常停止或受控重启对应服务 |
| 数据库数据文件、事务日志或未知文件 | 先查产品文档,不按普通文本日志处理 |
| 容器内应用持有的日志 | 确认宿主机 PID、容器及日志驱动,再按对应机制处理 |
磁盘已经满时,程序重开日志也可能失败。先确认目标目录存在、权限正确,并准备少量安全的写入余量。需要保留已删除日志时,优先使用其他磁盘或远程存储,别把备份又写进满盘分区。
四、以 Nginx 为例:重开日志比直接杀进程合适
Nginx 官方文档说明,主进程收到 USR1 后会重新打开日志,并通知工作进程处理日志文件。[5] 常规日志轮转也是先重命名日志,再让服务重新打开,而不是删完文件就不管。
如果已经确认持有文件的是当前 Nginx 实例,可以使用该安装所对应的二进制和配置执行:
# 适用于已确认默认二进制、配置、prefix 与 PID 文件属于当前实例的场景
sudo nginx -t
sudo nginx -s reopen
nginx -t 检查当前配置语法及相关文件访问,不会重启服务。自定义安装、多实例或容器部署,应使用对应的二进制、-c 配置路径及必要的 -p 前缀;不要让命令误操作另一实例。
如果选择直接向主进程发信号,必须先从实际配置的 PID 文件中确认并核对主进程身份。不要把 USR1 当成所有应用都支持的“刷新日志”信号,其他程序可能有完全不同的行为。
操作后查看错误日志、重新检查 lsof,并用真实请求确认新日志继续写入。若重开失败,先处理目录、权限或剩余空间,不要连续重复发送信号。
五、不支持日志重开的应用,如何安排重启?
有些程序只能退出后关闭日志文件。此时受控重启可能有用,但不是一个无成本的清理动作。单实例网站可能中断请求,队列任务可能需要重试,数据库还可能涉及恢复流程。
执行前至少确认这几项:
- 服务名、PID 与目标文件匹配;多实例应用是否都持有同一个 inode。
- 当前是否有长任务或事务;是否有维护窗口、流量切换和负责人。
- 日志是否需要留存;空间是否足够支持服务重新启动。
- 配置是否有效;若启动失败,是否准备好已知可用配置及回退方案。
确认之后,再按应用文档正常停止、启动,或使用其服务管理方式。本文不提供“一键重启所有服务”的脚本,也不建议 kill -9。强制终止可能把空间问题变成业务故障。
尤其不要随手向 /proc/<PID>/fd/<FD> 写入空内容,或对它执行截断。那会修改进程实际持有的文件,丢失日志;如果误认成数据库或其他业务文件,后果更严重。编号还可能变化,照抄旧记录并不安全。
六、做完以后,用三个结果验收
还是针对原来的文件系统检查:
df -hT /var/log
sudo lsof -nP +L1
sudo du -xhd1 /var/log
验收时不要只看磁盘使用率:
- 旧引用是否消失:目标设备、inode 对应的已删除文件是否不再被打开。
- 空间是否回落:观察
df的已用及可用空间,不必要求与du完全相等。 - 业务和新日志是否正常:真实请求、应用健康检查、新日志写入是否恢复。
只关闭了一个持有进程,其他进程还打开着同一文件,空间可能仍未释放。新日志又快速增长,也可能掩盖刚释放的容量。此时回到 PID 和 inode 逐项确认,比继续删目录更有效。
如果根本没有发现相关的已删除文件,还应检查:比较范围是否一致、du 是否因权限漏扫、挂载点是否遮住原目录文件,以及文件系统元数据、快照或保留空间等差异。遇到特定文件系统的异常,使用其官方诊断工具,不要盲目运行修复命令。
七、避免下次再靠手工删日志救急
检查现有 logrotate 或应用日志配置,重点不是“有没有生成旧文件”,而是轮转后程序是否转向新日志。可以先备份配置,再做只读调试检查:
sudo logrotate -d /etc/logrotate.conf
调试模式不会修改日志文件或状态文件,但正式执行前仍要审查匹配路径、权限和 postrotate 脚本。[6]
copytruncate 能用于某些不能重开日志的程序,但复制与截断之间存在丢日志的窗口,并不是零风险选项。[6] 审计、计费或其他不能漏记录的场景,要先评估是否接受这种取舍。
日常监控除了磁盘使用率,也应关注日志增长速度、保留周期和轮转任务失败。删除只是移除目录里的名字,关闭最后一个引用才可能释放这份文件的占用。 先找到仍持有它的进程,再选择业务能够承受的处理方式。
参考资料
资料核验日期:2026 年 10 月 10 日。本文示例未在读者的生产环境中执行。
- Linux man-pages:unlink(2),关于删除目录项与仍打开文件的生命周期。https://man7.org/linux/man-pages/man2/unlink.2.html
- GNU Coreutils:df invocation。https://www.gnu.org/software/coreutils/manual/html_node/df-invocation.html
- GNU Coreutils:du invocation。https://www.gnu.org/software/coreutils/manual/html_node/du-invocation.html
- lsof 项目手册,
+L1与打开文件信息字段。https://lsof.readthedocs.io/en/latest/manpage/ - Nginx 官方文档:Controlling nginx,Rotating Log-files。https://nginx.org/en/docs/control.html
- logrotate 项目手册,debug、postrotate 与 copytruncate。https://github.com/logrotate/logrotate/blob/main/logrotate.8.in




