为什么单台服务器上「磁盘满了」往往是别人的站点干的
个人站长常见的架构是「一台 VPS 跑好几个站」,或者「一台机器上既有网站又有备份目录、日志目录、还有给朋友开的 FTP 账号」。这种共享模式下最常见的事故不是硬盘坏了,而是某个站点的日志或者上传目录把整个分区写满,导致所有站点同时 500、MySQL 直接拒绝写入、甚至连 SSH 都登不进去(因为 root 也无法在自己家目录写 .bash_history)。
遇到这种情况,很多人的第一反应是「扩容」或者「加个磁盘清理脚本」。扩容当然有用,但它解决的是「空间不够」;如果问题是「某个用户无节制地占用」,扩容只是把这个定时炸弹往后推迟几周。真正对症的手段是磁盘配额(quota)——给每个用户、每个用户组设一个硬性上限,写到顶就返回 EDQUOT(Disk quota exceeded),只有肇事的那一个站点受影响,其他站照常运行。
这篇文章讲的是实战中真正会踩坑的部分:ext4 和 XFS 两套完全不同的配额工具链、aquota.user 与 prjquota 的取舍、以及为什么很多人执行完 quotaon 之后 repquota 还是空白。
先想清楚:按用户配额,还是按目录(项目)配额
Linux 的配额有三个维度,选错维度等于白做:
- 用户配额(usrquota):按 UID 限制。适合「一个用户一个站」的模型,比如
www-a、www-b各跑一个 PHP-FPM 池。这是最省事、最不容易搞错的一档。 - 组配额(grpquota):按 GID 限制。适合「多个用户共享一份额度」的模型,比如多个子站都用
www-data组,那就只能整体限制,无法区分谁用得多。 - 项目配额(prjquota,仅 XFS / ext4 支持项目 ID):按「目录树」限制,和 UID/GID 无关。这是真正符合「一个站点一个目录」直觉的做法,但配置门槛高:需要给目录打上
project ID,并且要求文件系统在挂载时就用prjquota选项(不能事后通过quotaon打开)。
我的建议:如果一台机器上的站点都跑在同一个 www-data 用户下(绝大多数宝塔/军哥 LNMP 环境就是这样),那么用户配额和组配额都帮不上你——因为它们只能限制整个 www-data 的总量,而你想限制的是其中的某一个站。这种情况下只有两条路:给每个站切一个独立 UID(推荐,顺便还能做 PHP-FPM 池隔离),或者上项目配额。
顺手说一句:如果一台机器上既有「网站目录」又有「备份目录」,而备份脚本写的是同一个分区,那么配额要特别小心——备份任务经常是半夜跑,一旦触发 EDQUOT,rsync 会报错退出,你以为备份成功了,实际上只备份了一半。所以配额策略要和备份路径规划一起考虑,最省心的做法是备份写到独立分区或对象存储,配额只压在用户可写的网站目录上。
ext4 配额:从 fstab 到 quotaon 的完整链路
ext4 的配额是最传统的 quota 工具链。很多人失败在第一步:配额必须在挂载时就声明,事后加参数不生效。
第一步,编辑 /etc/fstab,在目标分区的挂载选项里加上 usrquota,grpquota:
# 修改前
/dev/vdb1 /home ext4 defaults 0 2
# 修改后
/dev/vdb1 /home ext4 defaults,usrquota,grpquota 0 2
如果你用的是 UUID 挂载(云主机现在基本都是),那就改 UUID=xxxx 那一行,效果一样。改完必须重新挂载才能生效:
# 最稳妥:先检查 fstab 语法,避免重启后机器起不来
findmnt --verify --verbose
# 在线重挂载(只加选项、不动数据,sshd 不会断)
mount -o remount /home
# 确认选项已经挂上
findmnt /home -o TARGET,SOURCE,FSTYPE,OPTIONS
findmnt --verify 这一步千万别跳。写错一个字符,重启之后系统进 emergency mode,而你只能通过 VNC 控制台进去改,云服务商的 VNC 键盘输入还经常吞字符,修起来非常痛苦。
第二步,生成配额数据库文件。注意分区类型:quota.user 对应 ext3,aquota.user 对应 ext4。直接跑这条:
quotacheck -cugm /home
# -c 创建 -u 用户 -g 组 -m 跳过已挂载检查(remount -o ro 时用不到)
# 生成后会看到 /home/aquota.user 和 /home/aquota.group
ls -l /home/aquota.*
第三步,打开配额并验证:
quotaon -vug /home
# 查看是否真的开着
quotaon -p /home
# 期望输出包含:user quota on /home (/dev/vdb1) is on
如果 quotaon -p 显示 is off,只有三种原因:fstab 没加选项且没 remount、aquota.user 不存在、或者内核没编进 quota 支持(自建内核才可能遇到)。按这个顺序查,不要瞎试。
给用户设额度与读懂 repquota 的输出
设额度用 edquota -u 用户名,会打开一个编辑器:
Disk quotas for user www-a (uid 1002):
Filesystem blocks soft hard inodes soft hard
/dev/vdb1 1048576 2097152 3145728 12345 0 0
这里有两个容易搞混的概念:
- blocks / inodes:当前已用量(块以 KB 计,inodes 是文件个数)。这两个是只读的,你改不了。
- soft / hard:软限和硬限。硬限是绝对上限,一旦碰到立刻返回
EDQUOT,写入失败。软限是「警告线」,超过之后进入宽限期(grace period),宽限期内还能继续写,超过宽限期还没降到软限以下,软限就自动变成硬限。
宽限期默认 7 天,查看和修改方式:
# 查看当前宽限期
edquota -t
# 输出里可以改成 3days(个人站建议 1~3 天,7 天对日志类暴涨太宽松)
批量给多个用户设同样的额度,不用一个个编辑,先设好一个模板用户,再复制:
# 以 www-a 为模板,套用到 www-b 和 www-c
edquota -p www-a -u www-b www-c
日常巡检用 repquota -a,输出每一行都有 + 或 - 标记,+ 表示已经超限:
repquota -a -s
# -a 所有文件系统 -s 用 K/M/G 人类可读单位
把它包成一个每天跑的告警脚本,比在磁盘满了之后手忙脚乱强得多:
#!/bin/bash
# /usr/local/bin/quota-alert.sh —— 配额超限告警
set -euo pipefail
THRESHOLD=85
OUT=$(repquota -a -s 2>/dev/null | awk -v t="$THRESHOLD" '
/^\/dev\// { next }
NF >= 5 && $2 ~ /^[0-9]+$/ {
if ($3 > 0 && $2 / ($3*1024) * 100 > t) print $1, $2, $3
}')
if [ -n "$OUT" ]; then
echo "磁盘配额已超过 ${THRESHOLD}%:"
echo "$OUT"
# 这里接你的通知方式:邮件 / Server酱 / telegram bot
fi
注意 set -euo pipefail 里的 -u:如果脚本里引用了未定义的变量会直接退出,这在 cron 环境下反而比静默用空值更安全,但要求你把所有变量都赋初值。
项目配额:XFS 场景下的正确姿势
现在云主机默认很多是 XFS(CentOS/Rocky 9、部分 Ubuntu 镜像)。XFS 有自己的 xfs_quota 工具,和 quota 命令不通用,这是另一个常见困惑点。
XFS 挂载选项要写 uquota(用户)、gquota(组)、pquota(项目,用于目录级配额):
# /etc/fstab
/dev/vdb1 /data xfs defaults,uquota,pquota 0 2
mount -o remount /data
# XFS 的 quota 是挂载即生效,不需要 quotacheck / quotaon
项目配额的关键在于「给目录打标签」,三步走:
# 1. 定义一个项目 ID(比如 site-a = 1001),写入映射文件
echo "1001:/data/site-a" >> /etc/projects
echo "site-a:1001" >> /etc/projid
# 2. 初始化项目配额(只需要一次)
xfs_quota -x -c 'project -s site-a' /data
# 3. 设置硬限 5G、软限 4G
xfs_quota -x -c 'limit -p bsoft=4g bhard=5g site-a' /data
# 查看
xfs_quota -x -c 'report -p' /data
两个实战注意点:第一,project -s 会给目录树里所有现存文件打标签,目录越大耗时越久,生产环境建议在业务低峰做;第二,XFS 的项目配额对于「在配额目录里新建子目录」是自动继承的,这点比 ext4 传统配额好用,不用担心新建的目录漏掉额度。
另外提示一个坑:xfs_quota 的 -c 参数每次只能接一条命令,多条要用多个 -c,或者用交互模式 xfs_quota -x /data 进去敲。xfs_quota -x -c 'limit ...' -c 'report ...' 这样连写是可以的。
配额打开之后,为什么用户还能写满磁盘
这是实战里最让人崩溃的一幕:配额明明开了,repquota 显示用户只用了 2G(额度 5G),但 df 显示磁盘已经 100%。原因通常是以下四种之一:
- root 不受用户配额限制。这是设计如此:UID 0 对用户配额免疫。如果你的站点进程以 root 跑(有些一键包确实这么干),配额形同虚设。解决办法是让站点的 PHP-FPM / 服务跑在普通用户下,或者改用组配额并把进程
setgid过去。 - 被删除但未释放的文件句柄。
rm掉大日志后空间没回来,因为进程还握着 fd。用lsof +L1找到它,重启对应进程即可释放。 - 已被导出/挂载占用的 inode。另一种方向:块没满但 inode 满了(海量小文件),配额里的 inode 限额如果没设(soft/hard 为 0 表示不限),用户依然能把 inode 耗尽,导致全机无法创建新文件。
- 文件系统保留块。ext4 默认给 root 保留 5% 空间,
df里那部分对普通用户不可用。小磁盘(比如 40G)上 5% 就是 2G,看起来「明明有空间却写不进去」,可以用tune2fs -m 1 /dev/vdb1降到 1%。
所以配额不是一劳永逸,配套动作是:给用户同时设 inode 限额(不只是 blocks)、确保站点进程不以 root 运行、以及在第 5 条巡检里同时看 df -i。
小结:一套可以直接抄的落地清单
把上面所有内容压成一个可执行清单,个人站共享服务器场景按顺序做:
- 确认分区类型:
findmnt /home -o FSTYPE,ext4 走quota,XFS 走xfs_quota。 - fstab 加选项(ext4:
usrquota,grpquota;XFS:uquota,pquota),findmnt --verify后remount。 - ext4 跑
quotacheck -cugm+quotaon -vug,并用quotaon -p确认is on。 edquota -u设软硬限(建议软限 = 硬限 × 0.8),edquota -t把宽限期改到 1~3 天。- 多用户用
edquota -p批量套模板,别手工重复。 - 给每个用户同时设 inode 限额,防止小文件耗尽。
- 部署
repquota -a -s的每日告警脚本,85% 就通知。 - 确保站点进程不以 root 运行,否则配额对你无效。
磁盘配额算不上什么新技术,但它是「一台机器跑多个站」这个场景里性价比最高的一道隔离栏。它把「一个站搞死全机」变成「一个站自己写不进去」,而且代价只有一次 fstab 配置和十行巡检脚本。