⭐ 推荐:社区规则条款 V1.0

网站日志文件过大占满磁盘导致服务宕机怎么办?

liulian
liulian 归队站长新人站长
发布于 2026-10-14 21:17 ·1 浏览 ·3 回复
内容摘要

网站日志或 journal 占满磁盘致服务宕机时,先用 df、du、lsof 定位并 truncate 清空被占用日志恢复服务,再配置 logrotate 自动切割并限制 systemd journal 用量以防复发。

学完这篇,你能在十分钟内把被日志撑满的磁盘救回来,并配好自动轮转,让服务不再因为同一个原因第二次宕机。

第一步:先确认到底是不是磁盘满了

服务突然 502、SSH 登录报 No space left on device,八成就是磁盘满。登上去执行:

df -h

看哪个分区 Use% 是 100%,通常是 / 或 /var。接着定位是哪个目录吃掉的:

du -sh /var/log/* | sort -rh | head -20

如果 du 加起来只有几百 MB,但 df 显示满了,说明有文件被删了却还被进程占着句柄,用这条命令找:

lsof +L1 2>/dev/null | head -20

第二步:止血——清空文件,而不是删除文件

这一步的目标是立刻腾出空间让服务恢复。关键点是:日志文件正被 nginx/php-fpm 等进程打开着,直接 rm 不会释放空间,必须把文件内容截断:

truncate -s 0 /var/log/nginx/access.log

或者等价的写法:

: > /var/log/nginx/error.log

清完再 df -h 确认空间回来了,然后重启受影响的服务:

systemctl restart nginx

注意:如果你手快已经 rm 掉了文件,磁盘却没释放,用 lsof +L1 查到持有句柄的 PID,执行 systemctl restart <服务名> 或 kill -USR1 <PID> 让进程重新打开日志文件,空间才会真正还回来。

第三步:用 logrotate 做自动切割

logrotate 是绝大多数发行版自带的服务,配置文件放在 /etc/logrotate.d/ 下,一个服务一个文件。新建 /etc/logrotate.d/nginx,内容:

/var/log/nginx/*.log {
    daily
    rotate 7
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        [ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid)
    endscript
}

含义是:每天切一次,保留 7 份,压缩旧文件,切完后给 nginx 发 USR1 信号让它重新打开日志文件。

注意:kill -USR1 这行不能省。nginx 支持信号重开日志,但很多程序不支持,那种情况要用 copytruncate 参数替代 postrotate,代价是切割瞬间可能丢几行日志。两种方式不要同时用。

改完先干跑一次验证语法:

logrotate -d /etc/logrotate.d/nginx

确认没有报错,再强制执行一次:

logrotate -vf /etc/logrotate.d/nginx

最后确认定时任务在跑:

systemctl status logrotate.timer

第四步:管住 systemd journal 这个隐形大户

很多人清完 nginx 日志发现空间没回来多少,因为 /var/log/journal/ 才是大头。限制它的上限,编辑 /etc/systemd/journald.conf:

SystemMaxUse=500M
SystemKeepFree=1G
MaxRetentionSec=2week

保存后重启:

systemctl restart systemd-journald
journalctl --vacuum-size=200M

注意:journalctl --vacuum-size 只清旧日志,配置里的 SystemMaxUse 才是长期生效的阀门,两者都要做。

第五步:加一道监控,别等宕机才发现

写个简单脚本 /usr/local/bin/check_disk.sh:

#!/bin/bash
THRESHOLD=85
df -h | awk 'NR>1 {gsub("%","",$5); if ($5 > '"$THRESHOLD"') print $6" 使用率 "$5"%"}'

挂到 crontab 每 5 分钟跑一次,有输出就发邮件或推送到企业微信机器人:

*/5 * * * * /usr/local/bin/check_disk.sh | mail -s "磁盘告警" ops@example.com

注意:阈值别设 95%,那时候日志已经写不进去了。85% 告警,留出处理时间。

第六步:治本的几个方向

  • 把 nginx 的 access_log 级别调低,或者用 map 指令丢掉静态资源的记录,日志量能降一半以上。
  • 多台机器的话,用 rsyslog 把日志转发到独立的日志服务器(*.* @loghost:514),本机只留最近几小时。
  • 应用层把 debug 级别的日志关掉,log_level 从 debug 改成 warn。

小结

  • 磁盘满先用 df -h 定位分区,du -sh 和 lsof +L1 定位文件。
  • 清日志用 truncate -s 0,不要 rm;删了没释放就重启进程。
  • logrotate 配 /etc/logrotate.d/ 下的规则,nginx 记得 kill -USR1 重开日志。
  • journald 要在 /etc/systemd/journald.conf 里设 SystemMaxUse,不能只靠 vacuum。
  • 监控阈值设 85%,别等到 100% 才告警。
本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2010.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 3

juming
juming 归队站长新人站长 1楼 2026-10-14 21:27

这篇写得挺扎实,第四步应该是 systemd journal 的配额限制,我直接补上,再顺手说两个更容易踩的坑。

journald 的日志不走 logrotate,得单独管:先 journalctl --disk-usage 看占用,临时救急用 journalctl --vacuum-size=200M 或 --vacuum-time=7d;永久生效要编辑 /etc/systemd/journald.conf,设 SystemMaxUse=500M、SystemMaxFileSize=50M,然后 systemctl restart systemd-journald。

第一个坑:truncate -s 0 之后如果没让进程重新打开日志,nginx 会继续按原来的 offset 写,中间被填 0,文件变成一个巨大的稀疏文件——ls -lh 看着几个 G,du -sh 却很小。所以截断和 kill -USR1 必须配套做,别只做一半。

第二个坑:磁盘没满但服务照样挂,可能是 inode 用尽,df -h 看不出问题,得跑 df -i。日志目录小文件几十万是常见原因,这场景下压缩归档也没用,只能删。

治本的话,建议把日志目录单独挂一个分区或 LVM 卷,磁盘满只会拖垮日志而不是整台机器;nginx 里用 map 把静态资源(js/css/图片)的 access_log 关掉,通常能砍掉七八成体积。

chinaz
chinaz 归队站长新人站长 #1586 2楼 2026-10-14 21:38
juming:这篇写得挺扎实,第四步应该是 systemd journal 的配额限制,我直接补上,再顺手说两个更容易踩的坑。 journald 的日志不走 logrota…

juming 这两个坑补得很到位,尤其稀疏文件那条,很多人截断完 df 看着回来了就不管了,结果第二天一看 ls -lh 十几个 G,其实 du -sh 只有几 M,虚惊一场——要确认到底占没占空间,直接 du -sh 和 ls -lh 对一下,差值大就是稀疏文件,重启进程或者 kill -USR1 就正常了。

journald 那边再补一个小细节:--vacuum-size 不能低于 journald 内置的下限,设太小会直接报错;另外如果 /etc/systemd/journald.conf 里是 Storage=volatile,日志其实写在 /run/log/journal,那是 tmpfs 占内存,磁盘满的时候你反而看不到它,改配置前先 journalctl --disk-usage 确认路径。改完 journald.conf 必须 restart 才生效,systemctl reload 不行。

治本那两条我完全同意,再给一个更省事的:流量大的站别指望 daily 够用,logrotate 里把 daily 换成 maxsize 100M,或者 daily 加 maxsize 500M,让大小也能触发切割。nginx 侧除了关静态资源,还能顺手加缓冲,access_log /var/log/nginx/access.log combined buffer=64k flush=5s;,写盘次数能降一个量级,配上你那个 map 过滤,日志体积和 IO 一起下来。

最后提醒一句:systemctl status logrotate.timer 在部分容器或精简镜像里是没这个 timer 的,得挂 cron 跑 /etc/cron.daily/logrotate,部署完最好手动 logrotate -vf 一次确认真的在切。

域名注册
域名注册 版主归队站长新人站长 👑年卡会员 #1607 3楼 2026-10-14 22:01
chinaz:juming 这两个坑补得很到位,尤其稀疏文件那条,很多人截断完 `df` 看着回来了就不管了,结果第二天一看 `ls -lh` 十几个 G,其实 `du -s…

结论:这几条补充都站得住,我只在 size 与 maxsize 的区别、buffer 的丢日志风险、以及 volatile 场景下的参数名上再补三刀。

先说 logrotate 的参数。maxsize 和 size 不能混着理解:size 是排他的,一旦写上会忽略 daily/weekly 这些时间条件,只剩大小触发;要「时间或大小任一先到就切」必须用 maxsize。所以 daily + maxsize 500M 是对的组合,写成 size 500M 就变味了。还有个连带问题——按大小切时,流量一冲 rotate 7 可能只够留几个小时,日志保留窗口比你想的短得多,建议同时把 rotate 提到 30 左右,别让取证时发现旧日志全没了。

nginx 的 buffer=64k flush=5s 确实能降一个量级写盘,但它只是攒批,不减少日志总量。要注意进程被 kill -9 时缓冲区里没 flush 的行会丢,所以 buffer 别开到 1M 以上;flush 也别给太大,如果你需要靠日志实时看攻击流量,5 秒已经是能接受的上限了。

journald 那边补一点:Storage=volatile 时生效的是 RuntimeMaxUse / RuntimeKeepFree(作用在 /run/log/journal),写 SystemMaxUse 等于没设。--vacuum-size 设太小会被直接拒掉,这种场景改配置文件反而更干净。

容器里没 timer 那条也对,稳妥做法是部署完手动 logrotate -vf 跑一次,然后进目录确认 .1.gz 真的生成了——只看命令退出码不算数。