服务器磁盘满了怎么办

结论: 服务器磁盘满了要先保住系统可写,再按顺序释放空间:df -h 与 df -i 双查确认是空间耗尽还是 inode 耗尽,按"journal 日志 → 应用日志轮转 → 包缓存 → Docker → 临时文件 → 大文件"的顺序安全清理,用 truncate 而不是 rm 清空正在写入的日志,用 lsof | grep deleted 处理已删未释放的文件,最后用 LVM 在线扩容补上长期缺口。

引言:磁盘满是一件"会连锁恶化"的故障

磁盘满(Disk Full)和 CPU、内存告警不同,它不是"性能变差",而是会持续恶化的故障。当根分区使用率达到 100% 时:

  • 文件系统可能自动或被动转为只读(Read-only file system),所有写操作直接失败;
  • MySQL/PostgreSQL 会因为无法写临时文件或 binlog 而崩溃,且崩溃后可能无法正常启动;
  • SSH 登录可能失败,因为 PAM 会话与 sshd 需要写 /var/log、/tmp 和用户的 lastlog;
  • Docker 容器会集体退出,overlay2 层无法写入;
  • 定时任务(cron)执行失败并静默丢弃结果,你的备份脚本可能已经好几天没成功过。

更麻烦的是,磁盘满会让你失去排查手段:很多诊断命令本身需要写临时文件。所以处置顺序必须是"先恢复可写 → 再释放空间 → 最后找根因 + 扩容",而不是一上来就分析是什么占了空间。

本文的操作基于 Ubuntu 24.04 LTS、Rocky Linux 9 / CentOS Stream 9 等主流发行版(截至 2026 年),文件系统覆盖 ext4 与 XFS。

第一步:30 秒内确认是"空间满"还是"inode 满"

结论:磁盘满有两种完全不同的原因——块空间(block)耗尽和索引节点(inode)耗尽。两者的处理方法完全不同,必须先区分。

bash
# 双查:空间与 inode 一起看
df -h          # 看 Use% 列,100% 即空间满
df -i          # 看 IUse% 列,100% 即 inode 耗尽(文件数量太多)
df -hT         # 顺便看文件系统类型,后续扩容命令不同(ext4 用 resize2fs,XFS 用 xfs_growfs)

# 定位大目录:只在根文件系统内统计,按大小倒序
du -xh --max-depth=1 / 2>/dev/null | sort -hr | head -20

# 交互式分析神器(需安装)
ncdu /

两种情况的对照:

现象原因典型元凶处理方向
Use% = 100%,IUse% 正常块空间耗尽日志文件、Docker 镜像、备份包、core dump按本文顺序释放空间
IUse% = 100%,Use% 只有 40%文件数量耗尽 inode海量小文件:邮件队列、session 文件、容器层碎片、npm cache删除海量小文件目录,或重格式化时调大 inode 比例
两者都高长期未清理综合问题先清空间,再清小文件

如果 SSH 已经登不进去,或者登录后所有命令都报 No space left on device,先做两件事:

bash
# 1) 尝试重新挂载为可写(若提示 readonly,说明文件系统已被内核置为只读)
mount -o remount,rw /

# 2) 释放内核保留的 5% 空间(ext4 默认保留给 root,紧急时可临时调整为 1%)
tune2fs -m 1 /dev/sda1       # 仅 ext4 适用,能立刻多出约 4% 的空间

注意:tune2fs -m 只是应急手段,用在根分区上能换来几百 MB 到几 GB 的喘息空间,用完建议恢复到默认的 5%(tune2fs -m 5)。

第二步:系统完全进不去时,用救援模式

结论:磁盘满导致 SSH 无法登录时,物理机用单用户模式,云主机用控制台的 VNC 或"救援模式/救援实例"挂载原盘后清理。

物理机或 VPS(有 GRUB 界面)的操作方式:

text
1. 重启服务器,在 GRUB 菜单选中内核后按 e 进入编辑
2. 找到以 linux 开头(CentOS/Rocky)或 linux /boot/vmlinuz 开头(Ubuntu)的那一行
3. 在行尾追加以下任一参数:
   - systemd.unit=rescue.target      # 进入救援模式(推荐,会挂载文件系统并起基础服务)
   - systemd.unit=emergency.target   # 进入紧急模式(最精简,根分区可能只读)
   - init=/bin/bash                  # 直接进 shell(根分区默认只读,需 mount -o remount,rw /)
4. 按 Ctrl+X 或 F10 启动
5. 进入后先确认根分区可写:mount -o remount,rw /

云主机的推荐做法(阿里云、腾讯云、华为云等控制台均提供):使用"救援模式"或把系统盘挂载到另一台临时实例上,作为数据盘清理。这种方式不依赖原系统能否启动,最安全。

第三步:安全释放空间的顺序

结论:清理必须按顺序做——先清可再生的系统数据(日志、缓存),再动业务数据。顺序错了可能造成不可逆的数据丢失。

顺序清理对象命令典型可回收风险
1systemd journal 日志journalctl --vacuum-size=500M数百 MB ~ 数 GB无
2应用日志(轮转/截断)logrotate -f、truncate -s 0数 GB低(注意别 rm 正在写的日志)
3包管理器缓存apt-get clean / dnf clean all数百 MB无
4Docker 镜像/容器/日志docker system prune数 GB ~ 数十 GB中(会删未使用镜像与卷)
5临时文件清理 /tmp、/var/tmp数百 MB低(确认无运行中的临时文件)
6业务大文件(core dump、旧备份)人工确认后删除不定高,需逐个确认

1)先清 systemd journal

bash
# 查看日志占用
journalctl --disk-usage

# 保留最近 500 MB
journalctl --vacuum-size=500M

# 或只保留最近 7 天
journalctl --vacuum-time=7d

# 永久限制(写入 /etc/systemd/journald.conf)
sed -i 's/^#SystemMaxUse=.*/SystemMaxUse=500M/' /etc/systemd/journald.conf
systemctl restart systemd-journald

2)应用日志轮转与截断

bash
# 强制执行一次 logrotate(多数发行版已配置 nginx、rsyslog 等轮转规则)
logrotate -f /etc/logrotate.conf

# 手工截断某个已经很大的日志(保留 inode,进程仍持有 fd,业务不受影响)
truncate -s 0 /var/log/nginx/access.log
truncate -s 0 /var/log/myapp/app.log

# 批量截断超过 1 GB 的日志
find /var/log -type f -name '*.log' -size +1G -exec truncate -s 0 {} \;

# 找出所有超过 500 MB 的文件,人工确认后再处理
find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null | sort -k5 -hr | head -20

关键提醒:不要用 rm 删除正在被进程写入的日志。rm 只是删除目录项,进程持有的文件描述符(fd)还在,磁盘空间不会释放,而且进程还会继续往一个"看不见"的文件里写。正确做法是 truncate -s 0(原地清空,inode 不变)或先 logrotate 再让进程重新打开(reopen)。

3)包缓存与临时文件

bash
# Debian / Ubuntu
apt-get clean                      # 清空 /var/cache/apt/archives
apt-get autoremove --purge         # 删除不再需要的依赖包

# Rocky / CentOS / Fedora
dnf clean all
yum clean all

# 临时文件(systemd 自带的清理机制)
systemd-tmpfiles --clean
# 手工查看 /tmp 占用
du -sh /tmp /var/tmp 2>/dev/null

4)Docker 占用的专项清理

bash
# 先看 Docker 各类资源占用概览
docker system df

# 查看容器日志占用(往往是隐形大户)
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -hr | head -10

# 截断容器日志(不要直接 rm,容器进程持有 fd)
truncate -s 0 /var/lib/docker/containers//-json.log

# 清理未使用的镜像、容器、网络、构建缓存
docker system prune -a --volumes     # 谨慎:会删除所有未被使用的镜像和卷
docker image prune -a                # 只清镜像
docker builder prune                 # 只清构建缓存

# 限制容器日志大小(写入 /etc/docker/daemon.json 后重启 docker)
cat > /etc/docker/daemon.json <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "100m", "max-file": "3" }
}
EOF
systemctl restart docker

第四步:处理"已删除但未释放"的文件

结论:df 显示满了但 du 加起来对不上,几乎都是因为文件被删除后仍被进程持有。用 lsof 找出这些文件,清空或重启对应进程即可释放。

bash
# 列出所有已被删除但仍被进程打开的文件(第 8 列之后会显示 (deleted))
lsof -nP | grep -i '(deleted)'

# 更精确的写法
lsof +L1

# 只看某个挂载点的
lsof +L1 /data | head -20

处理方式有两种:

bash
# 方式一:直接清空该 fd(最安全,不需要重启进程)
# 假设输出中进程 PID=1234,fd=7w,文件大小为 20G
: > /proc/1234/fd/7        # 等价于把该 fd 指向的文件截断为 0

# 方式二:重启持有该文件的进程(会中断业务,放在最后)
systemctl restart myapp.service

如果整个系统有大量这类文件且无法逐个重启,最后手段是重启整机——但这应该是确认无其它办法后的选择。

第五步:inode 耗尽的特殊处理

结论:df -i 显示 100% 时,删除大文件没有用,必须删除数量多的文件。用分层统计找出小文件聚集目录。

bash
# 逐层统计文件数量,找出"小文件黑洞"
for d in /*; do echo "$(find $d -xdev -type f 2>/dev/null | wc -l) $d"; done | sort -rn | head -10

# 深入某个目录继续定位
for d in /var/*; do echo "$(find $d -xdev -type f 2>/dev/null | wc -l) $d"; done | sort -rn | head -10

# 找出文件数最多的单个目录
find / -xdev -type d -exec sh -c 'echo $(ls -f "$1" | wc -l) "$1"' _ {} \; 2>/dev/null \
  | sort -rn | head -15

inode 耗尽的常见元凶:

  • 邮件队列堆积:/var/spool/postfix/maildrop 或 /var/spool/clientmqueue,cron 任务输出未重定向时会产生海量小文件;
  • PHP / 应用 Session 文件:/var/lib/php/sessions 未配置回收(session.gc_maxlifetime);
  • Docker overlay2 碎片:层数过多且未 prune;
  • npm / composer / pip 缓存:~/.npm/_cacache、~/.cache;
  • 海量小图片、日志切片、未清理的监控数据。

删除海量文件时,rm -rf 会很慢且可能参数过长,用下面的方式更快:

bash
# 用 find + delete 分批删除(以 PHP session 为例)
find /var/lib/php/sessions -type f -mtime +1 -delete

# 极大量文件时用 rsync 技巧最快(先建空目录,再同步覆盖)
mkdir -p /tmp/empty && rsync -a --delete /tmp/empty/ /var/spool/postfix/maildrop/

同时建议给 cron 任务统一加上输出重定向,从源头杜绝 maildrop 堆积:

bash
# 错误的写法(输出会发给 cron 守护进程,进而产生邮件小文件)
*/5 * * * * /opt/scripts/check.sh

# 正确的写法(重定向到日志或丢弃)
*/5 * * * * /opt/scripts/check.sh >> /var/log/check.log 2>&1

第六步:在线扩容,把缺口补上

结论:清理只是应急,真正解决要靠扩容。使用 LVM(Logical Volume Manager)时可以在线扩容,业务不中断。

bash
# 1) 查看卷组剩余空间
vgs
vgdisplay

# 2) 扩展逻辑卷(两种方式选一)
lvextend -L +50G /dev/mapper/vg0-lv_root      # 增加 50 GB
lvextend -l +100%FREE /dev/mapper/vg0-lv_root # 用掉全部剩余空间

# 3) 扩展文件系统(注意 ext4 与 XFS 命令不同)
# ext4:参数是逻辑卷设备路径
resize2fs /dev/mapper/vg0-lv_root

# XFS:参数是**挂载点**,且 XFS 只能增大不能缩小
xfs_growfs /

# 4) 验证
df -h /

云主机的完整扩容链路(以云盘从 100 GB 扩到 200 GB 为例):

bash
# a) 先在云控制台完成云盘扩容,然后扩展分区
growpart /dev/vda 1

# b) 若使用 LVM,先扩展物理卷
pvresize /dev/vda2
lvextend -l +100%FREE /dev/mapper/vg0-lv_root
resize2fs /dev/mapper/vg0-lv_root

# c) 若未使用 LVM,直接扩展文件系统
resize2fs /dev/vda1          # ext4
xfs_growfs /                 # XFS

# d) 确认内核已识别新容量
lsblk && df -h

扩容前务必确认有可用的快照或备份,尤其是涉及分区表操作(growpart、fdisk)时。