☰
WordPress磁盘空间告急?inode使用率100%的排查与清理全攻略
2026/10/19 3:03:19 网站建设 项目流程

跑 WordPress 的站点,跑着跑着就容易收到主机商的邮件:磁盘空间告急。更让人头大的情况是——磁盘明明还有十来 G,但 inode 使用率已经到了 100%,后台传张图片都失败,日志里写着 “No space left on device”。这个场景我在不同主机上反复遇到,大多数时候,真正的元凶并不是某个巨型文件,而是缓存、日志、缩略图和数据库残留这些平时看不见的积累。这篇文章就围绕“释放 WordPress 磁盘空间并减少 inode 使用量”这个目标,把从排查到清理的完整路径捋一遍,适合跑站超过一年、空间时不时告急的人看。

1. 先搞清楚两个指标分别卡在哪

1.1 两个指标先拎清楚:inode 不是磁盘空间,而是“文件数量”

很多人一开始把 inode 当成一个很玄的概念,其实它相当于文件系统给每个文件发的“门牌号”。文件内容存在数据块里,文件叫什么、权限归谁、时间戳是多少、指向哪些数据块,这些信息都存在 inode 节点里。Linux 在格式化分区时就会预先分配好这批 inode 节点的数量,所以分区上能创建多少个文件(和目录)是有一个总量上限的。每个文件、每个目录、每个符号链接,哪怕内容是 0 字节,也要消耗一个 inode。于是就会出现一种很反直觉的状态:磁盘空间还剩几十 G,但 inode 用尽,什么都写不进去。

WordPress 之所以特别容易出现这个问题,是因为它的运行方式天然会产生大量小文件。页面缓存插件为了减少数据库查询,会在 wp-content 下按首页、文章页、分类页生成很多静态缓存文件;上传一张图片,主题和插件会按注册尺寸生成十几张不同规格的缩略图;调试模式一开,每次请求的 deprecation 警告都会往 debug.log 里追加;更新插件或核心时解压临时文件,如果中途报错还会留下残缺目录。这些文件单个可能只有几 KB,但数量可以轻松涨到几十万个。

所以清理之前,先要明确一个判断逻辑:如果磁盘空间满了,重点是找大文件;如果 inode 满了,重点是找小文件数量最密集的目录。这两个方向不能混着来。用df -h看磁盘空间,用df -i看 inode 使用率,两个命令的输出要并排看,才能判断当前到底是哪一种资源告急。曾经有个站点,管理员删了几个大压缩包,磁盘空间立刻降下来了,但第二天传文件还是失败,一查发现 inode 早就 100% 了,本质问题完全没解决。

1.2 三条命令摸清磁盘和 inode 的真实分布

拿到 SSH 权限或者主机商提供的终端之后,第一步是做一个快速体检。先看整体水位:

df -h / df -i /

然后进入站点根目录,按目录大小排序,把最占磁盘空间的目录拎出来:

cd /var/www/site du -xh --max-depth=2 . 2>/dev/null | sort -rh | head -30

这里的-x参数很容易被忽略,它的作用是只在当前文件系统里统计,避免把其它挂载点的内容也带进来造成误判。如果你站点目录跨了多个挂载点,建议先用mount确认站点所在分区再操作。

磁盘大目录找到了,接着看 inode 分布。可以把所有子目录都过一遍,统计每个目录下第一层文件的数量,大于一定阈值的输出排序:

for d in $(find /var/www/site/wp-content -type d); do c=$(find "$d" -maxdepth 1 -type f | wc -l) [ "$c" -gt 1000 ] && echo "$c $d" done | sort -rn | head -20

这个命令在目录很多时会跑得比较慢,可以把范围先锁定到wp-content/cache、wp-content/uploads这两个常见重灾区。如果你用的是纯面板环境、没有 SSH,那就在文件管理器里先看 wp-content 下各个目录的大小,但注意面板大多不给 inode 统计,inode 一旦到 100% 可能连面板自身的临时文件都写不进去,这种时候找托管商开个诊断通道才是正路。

2. 磁盘空间清理:优先动缓存、日志和旧备份

2.1 缓存目录:别一刀切,先看子目录再动手

清理缓存是性价比最高的操作,因为缓存本来就是“可再生资源”,它不是数据资产,删掉之后插件会在下一次访问时重新生成。但这里有一个经验教训:不要直接在 wp-content/cache 里执行rm -rf。不同插件会把不同类型的文件放进自己的子目录,有的放页面缓存,有的放 CSS/JS 合并缓存,有的放对象缓存落地文件。如果一股脑全删,页面可能暂时正常,但主题自定义样式、编辑器字体文件会突然丢失,因为那些恰好是编译后的样式缓存。

我比较推荐的命令行操作,是把整个缓存目录移到回收区,而不是原地删除:

mkdir -p /var/backups/site-trash mv wp-content/cache /var/backups/site-trash/cache_$(date +%s) mkdir -p wp-content/cache chown -R www-data:www-data wp-content/cache

这种“mv 到回收区”的做法有两个好处:一是如果站点报错,还能把目录原样移回来;二是真正释放空间的操作被延迟到回收区清理时,给你留出了观察窗口。如果你能登录插件后台,那更简单,直接在插件设置页点“清空缓存”,插件会自己把目录结构重新建好,顺便把对象缓存里的过期数据也刷一遍。这里额外提醒一句,清理页面缓存之后,如果站点用了 Redis 或 Memcached 做对象缓存,最好也 flush 一次对象缓存,否则页面可能在模板拼接阶段把旧数据带出来,前台出现样式错乱。

2.2 日志文件:清空比删除管用,防复发要改配置

日志是最容易被忽视的磁盘杀手。WordPress 自己的 debug.log 在 wp-content/debug.log,如果WP_DEBUG一直开着,几个月就能涨到好几 GB。先定位一下:

du -sh wp-content/debug.log find wp-content -name '*.log' -exec ls -lh {} \;

找到之后,很多人直接rm debug.log,但有时候会发现:文件明明删了,磁盘空间却一点没降。这是因为 PHP-FPM 进程还握着这个文件的句柄,删除操作只是把文件名从目录里摘掉,进程还在往已删除的 inode 上写,空间要等进程关闭句柄才会释放。更稳妥的做法是清空文件内容而不是删除文件本身:

truncate -s 0 wp-content/debug.log

清空之前如果想留底,先tail -n 500看一眼最新错误。然后还要检查 wp-config.php:

define('WP_DEBUG', false); define('WP_DEBUG_LOG', false);

生产环境本来就不该开着完整调试日志,关掉之后能避免第二天又涨回来。Web 服务器日志也一样,nginx 或 Apache 的 access.log、error.log 如果长期不轮转,单文件十几个 G 也不意外。建议配置 logrotate 按大小和天数双重触发:

/var/log/nginx/*.log { daily rotate 7 compress maxsize 100M missingok notifempty create 0640 www-data adm }

如果主机环境不让你碰 logrotate 配置,就用 cron 定期执行: > /var/log/nginx/access.log,同样能达到释放空间的效果。

2.3 备份文件:搬出网站根目录才是长久之计

很多站点在根目录下会留下backup-20250101.zip、site-backup.tar.gz这类文件。它们体积大,通常占的是磁盘空间而不是 inode,但危害不止占空间这一点——放在 Web 根目录里的备份包,一旦被搜索引擎收录或被恶意扫描器猜中路径,整站数据就全暴露了。

备份文件不是不能保留,而是不应该留在站点目录里。常规做法是下载到本地,或者上传到私有的对象存储,再从网站根目录删除。如果你确实需要在服务器本地留一份,放到 Web 根目录之外的目录,例如/var/backups/site/,并把目录权限设置成 700,只允许管理员访问。

这里有个细节容易掉坑:如果你的磁盘已经用了 95%,这时候千万不要直接执行打包备份。tar 打包需要额外的写入空间,磁盘满了之后打包必然失败,还会留下一堆半成品文件,把局面搞得更糟。正确的顺序是先清掉缓存、日志,或者至少把空间释放到 80% 以下,再执行备份。

3. inode 清理:重点瞄准上传目录和数据库

3.1 缩略图是怎么不知不觉堆成上万个文件的

上传目录是 WordPress 里 inode 消耗最恐怖的地方。原因很简单,WordPress 默认的媒体处理逻辑会为一张原图生成多个尺寸,加上主题自己注册的缩略图尺寸、页面构建器注册的裁剪尺寸,一张照片在 uploads 里可能对应十几个甚至几十个文件。更要命的是,以前用过的旧主题如果已经卸载,旧主题生成的那一批缩略图不会自动跟着删除,它们会一直留在 uploads 目录里变成死文件。

我自己接手过一个跑了四年的内容站,仅wp-content/uploads下的文件数量就超过了 9 万。查的时候用一条命令就能看到规模:

find wp-content/uploads -type f | wc -l

如果数量在几万级别,那就值得认真处理了。有个容易踩的坑是:不要看文件名带-300x200这种尺寸后缀就顺手删。当前主题正在使用的缩略图尺寸会直接关系到文章页和首页的布局,删掉之后图片裂了,排查半天才发现是清理脚本把主题缩略图也带走了。比较稳妥的方向是先用插件做“未使用媒体扫描”,它会先判断哪些附件 ID 已经不再被任何文章引用,再提示你清理对应的缩略图。

3.2 媒体库瘦身和归档的边界

处理 uploads 目录时,最忌讳的动作是“把 2021 年的目录整个删掉”。除非你已经确认那个年份的内容全部下线,且数据库里没有任何文章再引用这些图片,否则历史文章会瞬间大面积裂图。我在实际操作时先用 SQL 判断引用关系:

SELECT COUNT(*) FROM wp_posts WHERE post_status IN ('publish','private') AND post_content LIKE '%uploads/2021/%';

这个数字如果为 0,再确认媒体库里 2021 年的附件也都不是正在使用的状态,才有讨论归档的资格。即便如此,我也不建议直接删除,而是先把该目录打包放到站外:

tar -czf /var/backups/site/uploads-2021.tar.gz wp-content/uploads/2021

确认站点运行一段时间正常后,再从站点里移除原目录。如果某个年份的图片还在被大量引用,那思路就要从“删文件”变成“减体积”。历史文章里的原图经常是几 MB 一张,但页面展示实际只需要几千像素宽,可以用 ImageMagick 批量压缩:

mogrify -strip -quality 82 -resize '2048x2048>' /var/www/site/wp-content/uploads/2021/*.jpg

注意这个操作会改变文件的修改时间和内容,如果启用了 CDN,要留意 CDN 的缓存刷新策略,否则源站图片变了,CDN 边缘节点还沿用旧文件,用户看到的还是老版本。

3.3 数据库里的“软垃圾”:改动记录、自动草稿和过期临时数据

数据库文件本身不占虚拟主机的 inode 配额,但只要站点还在运行,它就在持续占用磁盘空间。很多人只会盯 wp-content 目录,忽略了/var/lib/mysql里那个已经膨胀到几 G 的库。WordPress 数据库里最常见的“软垃圾”有四类:

  • 文章修订版本 revision,每次编辑都会产生一份历史快照,长期积累可能比正文还多;
  • 自动草稿 autosave 和临时草稿,属于编辑过程中途产生的碎片;
  • 回收站里的文章和垃圾评论,删掉后默认不是立即清除,而是一直保存在表里;
  • 过期 transient,这是 WP 自己用来做临时缓存的选项数据,过期之后不清理会越积越多。

先用 wp-cli 看规模:

wp post list --post_type=revision --format=count wp comment list --status=spam --format=count

清理 revision 时,我通常只保留近一段时间的版本,老版本直接清掉。SQL 大体长这样:

DELETE FROM wp_posts WHERE post_type = 'revision' AND post_date < '2024-01-01 00:00:00';

如果你的表前缀不是默认的 wp_,SQL 里的前缀要对应修改。删完以后顺手处理一下孤儿 postmeta:

DELETE pm FROM wp_postmeta pm LEFT JOIN wp_posts p ON pm.post_id = p.ID WHERE p.ID IS NULL;

这条清理的是已经不存在于 wp_posts 的残留元数据,但因为涉及跨表关联,执行之前务必备份数据库。过期 transient 可以直接用 wp-cli 清理:

wp transient delete --expired

对于优化数据库表,wp db optimize可以做一次,但 InnoDB 表执行 optimize 的收益不一定立竿见影,更关键的还是先把自动加载的大选项清理掉。可以查一下 wp_options 里哪些 autoload 选项的体积特别大:

SELECT option_name, LENGTH(option_value) AS len FROM wp_options WHERE autoload='yes' ORDER BY len DESC LIMIT 20;

看到类似_transient_*这种明显是临时缓存的选项,确认过期后可以删。另外,为了防止未来 revision 继续膨胀,建议在 wp-config.php 里加一行:

define('WP_POST_REVISIONS', 5);

这个常量能把每次编辑保存的历史版本数量限制在 5 个以内,比定期清理省心得多。

4. 实操:一天内完成清理的脚本、备份与验证流程

4.1 清理前四个安全动作,一个都不能省

磁盘清理这件事,看起来是运维操作的终点,但真正决定成败的是操作开始之前。我在执行任何清理任务之前,固定会做四个动作。

第一,把站点切换成维护模式,避免清理过程中还有用户在前台写入评论、上传图片、提交表单。wp-cli 一条命令就能完成:

wp maintenance-mode activate

第二,备份数据库。即使你只打算清理缓存和日志,也要备份数据库,因为你无法预料某个清理插件或手误会不会顺带动到数据表。备份文件必须放到站点目录之外,最好直接下载到本地。

第三,做文件备份,但要做到“精准排除”。备份时不要无脑打包整个站点目录,否则缓存、debug.log、旧备份全被一起打包,等于把垃圾又复制了一份。rsync 同步时排除掉wp-content/cache、*.log、*.zip、*.tar.gz,只保留真正需要的内容。

第四,记录清理前的df -h和df -i输出。这一步很多人嫌麻烦,但清理完比对前后数值时才会发现,没有基准数据你根本说不清到底清掉了多少东西。

4.2 可复制的清理脚本示例

下面这个脚本是我实际使用过的框架,梳理成通用版本方便各位按自己站点目录调整。需要注意,脚本里的路径是占位符,直接跑之前务必先核对。

#!/bin/bash # 清理 WordPress 磁盘空间与 inode 使用量 SITE=/var/www/site TRASH=/var/backups/site-trash mkdir -p "$TRASH" echo "===== 清理前 =====" df -h / | tail -1 df -i / | tail -1 # 1) debug.log 保留最新错误到单独文件,然后清空 tail -n 500 "$SITE/wp-content/debug.log" > /tmp/debug_tail.txt 2>/dev/null truncate -s 0 "$SITE/wp-content/debug.log" 2>/dev/null || true # 2) 页面缓存目录整体移入回收区,原地重建空目录 if [ -d "$SITE/wp-content/cache" ]; then mv "$SITE/wp-content/cache" "$TRASH/cache_$(date +%s)" mkdir -p "$SITE/wp-content/cache" chown -R www-data:www-data "$SITE/wp-content/cache" fi # 3) 清理过期临时数据 cd "$SITE" if command -v wp >/dev/null 2>&1; then wp transient delete --expired fi # 4) 清理 7 天前的临时文件 find "$SITE/wp-content/tmp" -type f -mtime +7 -delete 2>/dev/null || true find /tmp -maxdepth 1 -type f -name 'wp-*' -mtime +7 -delete 2>/dev/null || true echo "===== 清理后 =====" df -h / | tail -1 df -i / | tail -1 echo "回收目录:$TRASH" echo "确认站点无异常后,再清理回收目录中的内容"

脚本里 mv 缓存目录而不是直接删,是个很有效的中庸方案。脚本执行完后,回收目录还会占用一样多的磁盘空间,所以这只能算“半释放”。要真正把空间拿回来,必须在确认站点正常之后,把回收目录里那些子目录删掉。

4.3 清理完成后,这些线上状态必须逐个确认

清理完不是 df 数值降下来就算结束。我一般会按这个顺序做一次线上健康检查:

  • 前台打开首页和几篇带图的文章,确认页面渲染正常、没有裂图。
  • 后台打开媒体库,检查缩略图网格是否正常显示。
  • 主题设置页和页面构建器编辑页各开一次,确认 CSS/JS 缓存正常。
  • 发布一条测试草稿,确认编辑保存不报错。
  • 退出维护模式之后,观察十分钟的错误日志,确认没有新的致命错误。

如果清理之前用 CDN,清理之后还要把首页刷一次缓存,让 CDN 重新回源。文件缓存被清掉以后,第一次请求会比平时慢一些,这是正常现象,不是故障,但如果有用户正好在那几分钟访问,体验上会觉得“站点变慢了”。所以大清理这种事,尽量安排到访问低峰期做。

5. 经验沉淀:排障实录与自动化防复发

5.1 删完磁盘没降?先查被进程占用的文件

清理之后发现磁盘占用纹丝不动,这个问题基本每个运维过的人都遇到过。最典型的原因是日志文件在被进程打开的状态下被删除了。删除动作只是摘掉了目录里的文件名,进程仍然持有这个文件的句柄,磁盘空间被标记为“已释放”要等进程关闭句柄之后才真正回收。

排查方法是用 lsof 找出所有“已删除但被占用”的文件:

lsof +L1 | grep deleted

看到进程号之后,确认是哪个服务在占用,例如 php-fpm 或 nginx。如果确认可以重启,就重启对应服务;如果不想中断服务,那就定位到具体文件,用清空文件的方式优先处理,而不是再次删一遍。另一个容易忽略的原因是站点目录跨挂载点,你清理了 A 目录,但实际占满的是 B 分区,检查时记得对照df -h的输出路径,别被根目录使用率误导。

5.2 inode 清完又反弹,问题出在“持续制造”而不是“历史遗留”

有时候 inode 清理完没两天又回到 100%,这种反复出现的情况,说明站点的某个进程在持续产生新文件,而不是简单的历史垃圾堆积。定位方法很直接:对怀疑目录统计文件数量,隔几分钟再统计一次,看数字是否在增长:

find /tmp -type f | wc -l find /tmp -type f | wc -l # 5分钟后再跑一次

如果数量上涨,再看最近几分钟内新文件到底是谁写出来的:

find /tmp -type f -mmin -5 | head -50

我遇到过的几类常见“制造者”包括:插件开启了调试并把每次请求的错误写入单独文件;邮件发送失败后进入重试队列,不断生成临时文件;某个页面缓存插件每次访问都会按随机字符串生成缓存文件,而清理机制没有正常执行。找到文件生产源之后,处理思路就从清理切换到修复配置,否则你就是在给一个漏水的桶反复舀水。

5.3 把清理做成每周巡检的例行动作

与其等到主机商警告邮件来了再抢救,不如把清理动作固化到 cron 里。最简单的组合可以这样:

0 4 * * * cd /var/www/site && wp transient delete --expired >> /var/log/wp-cron.log 2>&1 0 4 * * 1 cd /var/www/site && truncate -s 0 wp-content/debug.log 2>/dev/null || true

磁盘和 inode 还有一个更可靠的防线是监控脚本,每天定时跑一次:

#!/bin/bash disk=$(df -h / | awk 'NR==2 {print int($5)}') inode=$(df -i / | awk 'NR==2 {print int($5)}') if [ "$disk" -gt 80 ] || [ "$inode" -gt 80 ]; then echo "站点资源告警:磁盘 ${disk}% / inode ${inode}%" | mail -s "WP资源告警" admin@example.com fi

告警阈值设定在 80% 比较合理,因为到了 90% 以上再做清理,很多操作会受限于写入空间不足,处理起来非常被动。每季度也可以挑个周末把 uploads 目录按年份过一遍,看看有没有已经彻底没用的归档目录,该转移到冷存储就转移,站点长期跑下来会轻松很多。

最后分享一条我自己的习惯:遇到拿不准能不能删的文件,我不会立刻放手删,而是先把它mv到回收目录,记录好原来位置和日期,保留 48 小时。48 小时后如果站点没有任何异常,再真正删除。这套“回收目录 + 观察窗口”的做法,让我在多次清理任务里既达到了瘦身目的,又避开了“删完才发现某个目录还有用”这种尴尬局面。磁盘和 inode 的释放,本质上并不难,难的是在动手前想清楚哪些能碰、哪些不能碰,以及给每一次清理留好后路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询