服务器磁盘空间不够怎么清理

结论: 服务器磁盘清理的正确顺序是:先用 df -h 确认是哪块分区满、df -i 确认是不是 inode 耗尽,再用 du -sh * | sort -hr 或 ncdu 逐层定位占用目录,然后按"日志 → Docker → 包缓存 → 应用数据"的顺序清理,最后用 logrotate 建立长效机制防止复发。删了大文件空间却没释放时,用 lsof | grep deleted 找出仍占用句柄的进程并重启它。

磁盘满不是小问题。系统盘写满后,MySQL 会崩溃、systemd 无法写日志、SSH 登录会卡在认证环节,甚至连 sudo 都可能因为无法写临时文件而失败。更麻烦的是,很多人一上来就 rm -rf 各种目录,结果要么删错了正在使用的数据,要么删完发现 df -h 显示的占用一点没降。本文按"定位 → 排查 → 清理 → 防复发"四段式组织,给出每一步的可复制命令与安全边界。

第一步:确认到底是哪块盘满了

结论:不要一上来就删文件,先用 df -h 确定挂载点、用 df -i 排除 inode 耗尽的可能。 这两者表现相似但处理方式完全不同。

bash
# 查看各分区空间占用(-h 人性化单位,-T 显示文件系统类型)
df -hT

# 查看 inode 使用情况(重点看 IUse% 列)
df -i

# 只看根分区与使用百分比
df -h / | awk 'NR==2 {print $5}'

# 快速找大于 500MB 的单个文件
sudo find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null | sort -k5 -hr | head -20

两种"满"需要区分:

  • 空间满:df -h 中 Use% 达到 100%,处理方式是删除大文件。
  • inode 满:df -h 还有剩余空间,但 df -i 的 IUse% 到 100%,创建文件时报 No space left on device。inode 是文件索引节点,每个文件占一个,海量小文件(如 PHP session、邮件队列、容器镜像层)会耗尽 inode。处理方式是删除包含大量小文件的目录,而非删几个大文件。

-xdev 参数表示不跨文件系统,避免把挂载的数据盘、网络存储也扫一遍,既慢又可能误判。

第二步:按目录排查谁在占空间

结论:从根目录下逐层使用 du 定位,或直接装 ncdu 交互查看,两者结合效率最高。 du 统计的是目录实际占用,df 统计的是文件系统分配,两者数值不一致时通常意味着存在"已删除但被进程占用"的文件。

bash
# 查看根目录下各一级目录占用并排序
sudo du -h --max-depth=1 / 2>/dev/null | sort -hr | head -15

# 深入 /var 查看(日志与缓存的重灾区)
sudo du -sh /var/* 2>/dev/null | sort -hr | head -10

# 查看当前目录下各子项占用
sudo du -sh * 2>/dev/null | sort -hr

# 交互式工具,支持键盘进入子目录、d 键删除(需先安装)
sudo apt install -y ncdu      # Ubuntu / Debian
# sudo dnf install -y ncdu    # CentOS Stream 9
ncdu /

按目录占用的排查顺序表

结论:按下面这张表的顺序排查,能用最少时间覆盖 90% 的实际占用来源。 顺序按"出现频率 × 可安全清理程度"排列。

排查顺序目录常见占用来源处理动作风险等级
1/var/logjournald 日志、Nginx/Apache 访问日志、应用日志journalctl --vacuum-size、logrotate、truncate低
2/var/lib/docker无用镜像、停止的容器、容器 json 日志、构建缓存docker system prune、truncate 容器日志中
3/var/cacheapt/dnf 包缓存、pip/npm 缓存apt clean、dnf clean all低
4/tmp临时文件、上传残留、崩溃转储systemd-tmpfiles --clean低
5/var/lib/mysqlbinlog、慢查询日志、ibdata、表碎片PURGE BINARY LOGS、清理慢日志高
6/boot旧内核残留(100~300MB/个)apt autoremove --purge中
7/home、/data用户上传、备份文件、下载的安装包人工确认后删除或转对象存储高
8/root、各用户目录忘记删除的大备份、core dump人工确认后删除高

第三步:清理 systemd 日志(journald)

结论:journald 日志是最常见的空间杀手,用 journalctl --vacuum-size=500M 即可安全瘦身,并建议写入配置文件形成长期约束。 这一步对系统无任何副作用,可以放心执行。

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

# 保留最近 500MB(按体积裁剪,最常用)
sudo journalctl --vacuum-size=500M

# 或按时间裁剪:只保留最近 7 天
sudo journalctl --vacuum-time=7d

# 先演练不实际删除(--verify 检查完整性)
sudo journalctl --vacuum-size=500M --verify

写入配置实现永久限制:

ini
# /etc/systemd/journald.conf
[Journal]
SystemMaxUse=500M
SystemKeepFree=1G
MaxRetentionSec=7day
ForwardToSyslog=no
bash
sudo systemctl restart systemd-journald
journalctl --disk-usage        # 确认已降至目标值

第四步:清理 Docker 占用

结论:Docker 的空间占用主要来自无用镜像、已停止容器和容器日志,用 docker system df 先看清楚,再用 prune 系列命令回收。 注意:-a 会删除所有未被使用的镜像,--volumes 会删除数据卷,生产环境执行前必须确认数据卷中没有需要保留的数据。

bash
# 查看 Docker 各类资源的占用明细
docker system df

# 安全清理:删除停止的容器、悬空镜像、无用网络与构建缓存
docker system prune

# 彻底清理:额外删除所有未被容器引用的镜像
docker system prune -a

# 单独清理数据卷(危险:会删除未挂载的卷数据)
docker volume prune

# 只清理构建缓存(BuildKit)
docker builder prune

# 容器 json 日志往往是隐形大户,逐个清空(不删文件,避免容器写入失败)
sudo truncate -s 0 /var/lib/docker/containers/*/*-json.log

为防止容器日志无限增长,建议在 /etc/docker/daemon.json 中统一限制:

json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "3"
  }
}

修改后执行 sudo systemctl restart docker 生效(仅对新创建的容器有效,已有容器需重建)。

第五步:清理应用日志并配置 logrotate

结论:访问量大的站点,Nginx 访问日志一天就能涨几个 GB。正确做法不是定期手工删,而是配置 logrotate(日志轮转)让它自动压缩、保留、删除。

bash
# 应急:清空正在写入的日志(用 truncate 而不是 rm,避免进程句柄失效)
sudo truncate -s 0 /var/log/nginx/access.log
sudo truncate -s 0 /var/log/nginx/error.log

# 查看各应用日志的体积
sudo du -sh /var/log/* 2>/dev/null | sort -hr | head -10

# 手工触发一次轮转(-d 为调试模式,不实际执行)
sudo logrotate -d /etc/logrotate.d/nginx
sudo logrotate -f /etc/logrotate.d/nginx

一份标准的 Nginx logrotate 配置如下,若文件已存在则按需修改保留天数:

conf
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily                 # 每天轮转
    rotate 14             # 保留 14 份
    compress              # 压缩旧日志
    delaycompress         # 延后一份再压缩,避免正在写入
    missingok             # 文件不存在不报错
    notifempty            # 空文件不轮转
    create 0640 www-data adm
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
    endscript
}

postrotate 中的 kill -USR1 是让 Nginx 重新打开日志文件的关键,缺少它会导致日志继续写进已被重命名的旧文件,空间依旧不降。

第六步:清理包缓存、临时文件与旧内核

结论:这一组操作风险最低、见效最快,可以放心执行。 包缓存与旧内核加起来常常能释放 1~3GB。

bash
# ===== Ubuntu 24.04 / Debian 12 =====
sudo apt clean                     # 清空 /var/cache/apt/archives
sudo apt autoclean                 # 只删过期包
sudo apt autoremove --purge -y     # 删除不再需要的依赖与旧内核
sudo dpkg -l 'linux-image-*' | grep '^ii'   # 确认剩余内核

# ===== CentOS Stream 9 / Rocky Linux 9 =====
sudo dnf clean all
sudo dnf autoremove -y

# 清理语言包与缓存(可选)
sudo rm -rf /var/cache/pip /root/.cache/pip

# 清理 /tmp:推荐用 systemd 的临时文件清理机制,比直接 rm 安全
sudo systemd-tmpfiles --clean
sudo find /tmp -type f -atime +7 -delete

# 清理 core dump(崩溃转储,单个可达数 GB)
sudo find / -xdev -name 'core.*' -size +100M -delete 2>/dev/null

需要提醒的是,直接 rm -rf /tmp/* 有风险:/tmp 下可能存放着 MySQL socket、SSH agent、PHP-FPM socket 等正在使用的文件,删除后对应服务会异常。优先用 systemd-tmpfiles --clean 或按访问时间删除 7 天前的文件。

特殊情况:删了大文件但空间没有释放

结论:这是 Linux 的经典陷阱——文件被删除后,如果仍有进程持有它的文件句柄,磁盘块不会被回收。 df -h 依旧显示 100%,但 du 已经统计不到那个文件,两者差值就是被占用的空间。

bash
# 找出所有"已删除但仍被进程占用"的文件
sudo lsof | grep deleted

# 只看大文件的(带 size 列,单位为字节)
sudo lsof -nP | grep deleted | awk '$7 > 104857600'

# 查看某进程打开的文件描述符
sudo ls -l /proc//fd/ | grep deleted

处理方式有二,按影响从小到大排列:

  1. 在线清空(推荐,服务不中断):找到对应的 fd 编号后执行 sudo truncate -s 0 /proc//fd/,把文件内容清零,空间立即释放,进程无需重启。
  2. 重启进程:sudo systemctl restart nginx(或对应服务名),进程关闭句柄后空间自动回收。生产环境应在低峰期执行。

注意:不要重启数据库来释放空间,正确做法是先在数据库内部清理(如 PURGE BINARY LOGS),重启 mysqld 可能导致数分钟的不可用与缓冲池冷启动带来的性能骤降。