有一种 VPS 故障很容易把人绕晕:
No space left on device
你第一反应是磁盘满了,于是执行:
df -h
结果发现 / 分区只用了 60%,空间明明还够。可网站上传失败、数据库写不进去、Nginx 缓存报错、PHP session 创建失败,甚至 touch test.txt 都失败。
这时候要怀疑的不是磁盘容量,而是 inode。
简单说,磁盘空间管“文件内容能放多少”,inode 管“能创建多少个文件”。如果服务器上堆了几百万个小文件,空间可能还没用完,inode 已经先被耗尽。Linux 仍然会报 No space left on device,因为对文件系统来说,它已经没有位置登记新文件了。
先看空间:
df -h
再看 inode:
df -i
df -h 看的是容量,df -i 看的是 inode。GNU Coreutils 的 df 文档里也把 -i / --inodes 单独列为 inode usage 信息。
你可能会看到类似结果:
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 1310720 1310500 220 100% /
这种情况就是典型 inode 快用完或已经用完。此时哪怕 df -h 还有几十 GB 空间,也可能无法创建新文件。
可以再做一个安全测试:
touch /tmp/inode-test
如果这里也报 No space left on device,而 df -i 显示 100%,基本就坐实了。
inode 通常不是被大文件吃掉,而是被大量小文件吃掉。
VPS 上常见来源有这些:
- PHP session 文件
- Nginx / CDN / 反向代理缓存
- Docker overlay2 小文件
- npm、pip、composer 缓存
- 邮件队列
- 临时上传目录
- 爬虫、采集、日志切片产生的小文件
- 应用把每次请求、每个任务、每条消息都落成独立文件
一个 2 KB 的小文件也要占一个 inode。几十 GB 的大视频可能只占少量 inode,但几百万个 session 文件能很快把 inode 打满。
inode 故障最危险的操作,是不知道谁占用 inode 就直接删目录。
尤其不要盲删这些路径:
/var/lib
/etc
/usr
/home
/var/lib/docker
有些目录看起来很大或文件很多,但里面可能是数据库、Docker 层、包管理器状态或正在运行的服务数据。删错了,inode 问题没解决,系统先坏了。
正确顺序是:先定位,再清理;先清理缓存和临时文件,再考虑业务数据。
从根分区开始统计,每一层看文件数量:
sudo find / -xdev -type f | cut -d/ -f2 | sort | uniq -c | sort -nr | head
如果 /var 最多,再往下看:
sudo find /var -xdev -type f | cut -d/ -f2-3 | sort | uniq -c | sort -nr | head
继续细分:
sudo find /var -xdev -type f | cut -d/ -f2-4 | sort | uniq -c | sort -nr | head
这类命令的思路不是看文件大小,而是看“文件数量”。因为 inode 被小文件耗尽时,du -sh 不一定能快速暴露问题。
如果你怀疑某个目录,比如 PHP session:
sudo find /var/lib/php/sessions -type f | wc -l
如果怀疑 Docker:
sudo find /var/lib/docker -xdev -type f | wc -l
如果怀疑 Nginx 缓存:
sudo find /var/cache/nginx -type f | wc -l
有时候你已经删了文件,但空间没有释放。原因是进程还打开着这些文件。
可以看:
sudo lsof +L1
这条命令会列出 link count 小于 1、也就是已经从目录里删除但仍被进程占用的文件。它更常用于容量释放问题,但在排查磁盘相关异常时也值得看一眼。
如果发现某个日志文件被进程占着,通常不要直接杀进程,先判断服务类型。对 Nginx、PHP-FPM、应用服务,优先考虑 reload 或 restart:
sudo systemctl reload nginx
sudo systemctl restart php8.2-fpm
sudo systemctl restart app
重启前先确认这是不是生产服务,避免无意中中断业务。
如果 systemd journal 占用很多文件,可以先看它的磁盘使用:
journalctl --disk-usage
清理旧日志可以用 systemd 官方支持的 vacuum 参数,例如只保留 7 天:
sudo journalctl --vacuum-time=7d
或者限制总大小:
sudo journalctl --vacuum-size=500M
不要直接去 /var/log/journal 里面手工乱删。journalctl --vacuum-* 是更安全、可控的方式。
如果你发现 journal 经常涨得很快,要继续追问:是不是某个服务在疯狂报错?可以用:
journalctl -p warning --since "24 hours ago" --no-pager
systemctl --failed
清理日志只是止血,真正要修的是持续刷日志的服务。
Docker 是 inode 消耗大户,尤其是频繁 build、频繁拉镜像、容器日志不限制、overlay2 层堆积时。
先看 Docker 占用:
docker system df
列出容器:
docker ps -a
清理前先确认哪些镜像、容器、volume 还要用。相对安全的第一步是清理停止的容器、未使用网络、悬空镜像和 build cache:
docker system prune
如果你确定未使用镜像也可以删,再用:
docker system prune -a
注意:-a 会删除所有当前未被容器使用的镜像。对生产环境来说,最好先确认部署回滚是否依赖旧镜像。
不要直接删除 /var/lib/docker/overlay2。这会破坏 Docker 的内部状态,可能导致容器无法启动。
更好的预防方式,是给容器日志加限制。例如 Docker daemon 配置:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
改完后需要重启 Docker:
sudo systemctl restart docker
重启 Docker 会影响容器,生产环境要安排窗口。
如果 inode 被 PHP session 吃掉,先确认 session 目录:
php -i | grep session.save_path
常见位置:
/var/lib/php/sessions
看数量:
sudo find /var/lib/php/sessions -type f | wc -l
清理旧 session 时,不要直接全部删除。可以先删 7 天前的:
sudo find /var/lib/php/sessions -type f -mtime +7 -delete
如果是缓存目录,也尽量按时间清理:
sudo find /path/to/cache -type f -mtime +7 -delete
如果目录文件数量巨大,find 本身也可能跑很久。建议低峰期执行,并先用不带 -delete 的命令预览:
sudo find /path/to/cache -type f -mtime +7 | head
确认没问题再删除。
APT 缓存通常不是 inode 最大来源,但可以作为辅助清理。
查看缓存目录:
sudo du -sh /var/cache/apt
清理下载包缓存:
sudo apt clean
清理不再需要的依赖要更谨慎:
sudo apt autoremove
autoremove 可能删除你不再显式依赖但仍在使用场景里重要的包。生产机器执行前要看清楚将删除的列表。
如果网站已经不能写 session、不能上传、数据库不能写,你可以按这个顺序止血:
df -i确认 inode 是否 100%- 找出 inode 最多的目录
- 优先清理明确的临时文件、缓存、旧 session
- 如果是 Docker,先用
docker system df,再决定 prune 范围 - 如果是日志,优先用
journalctl --vacuum-*和 logrotate - 清理后再执行
df -i验证 - 重启或 reload 受影响服务
验证:
df -i
touch /tmp/inode-test
rm /tmp/inode-test
systemctl --failed
如果 inode 从 100% 降下来,并且 touch 成功,说明文件系统已经恢复创建文件的能力。
预防比事后清理重要。
建议做这些:
- 监控
df -i,不要只监控df -h - 给 Docker 日志设置
max-size和max-file - 给 systemd journal 设置保留时间或大小
- 配好 logrotate,避免日志无限切片
- 对 session、cache、upload tmp 设置定期清理
- 小文件特别多的业务,不要都放系统盘
- 对采集、爬虫、任务队列类程序限制文件落盘数量
- 备份时不要只备大文件,也要关注小文件数量
如果业务天然会产生大量小文件,比如图片缩略图、爬虫缓存、邮件队列、对象切片,应该考虑:
- 单独挂载数据盘
- 使用 XFS 等更适合某些场景的文件系统
- 把静态对象放对象存储
- 把临时数据放 Redis / 数据库 / 队列系统
- 定期归档或压缩旧小文件
遇到 No space left on device,但 df -h 看起来还有空间时,按这个顺序查:
-
df -h看容量 -
df -i看 inode -
touch /tmp/inode-test验证能否创建文件 -
find统计哪个目录小文件最多 - 检查 PHP session、缓存、Docker、journal、邮件队列
- 清理前先预览,不要直接
rm -rf - 清理后再次
df -i - reload/restart 受影响服务
- 给 inode、日志、缓存、Docker 日志加监控和保留策略
inode 问题的核心不是“磁盘不够大”,而是“文件数量失控”。当你看到空间还有、文件却写不进去时,别只盯着 df -h,马上补一条 df -i。很多看似玄学的 VPS 写入失败,答案就在这里。
