一、为什么要做日志轮转
VPS 上运行的 Nginx、MySQL、PHP-FPM、系统 journal 等每天都在写入日志,如果不加控制,/var/log 目录会被撑爆,轻则磁盘告警,重则服务写入失败直接宕机。日志轮转(log rotation)就是按时间或体积把日志"切一刀":保留最近若干份,旧的自动压缩归档甚至删除,让磁盘占用始终维持在可控范围。
以日志体量最大的 Nginx 为例,单机日访问量 10 万 PV 时,access.log 一天就能增长 300MB~1GB。不做轮转,一块 40G 的系统盘半个月就会被塞满。本文从 Linux 自带的 logrotate 讲起,覆盖配置、手动调试与常见排错。
二、logrotate 的核心机制
logrotate 本身是一个由 cron 每天定时触发的程序,主配置在 /etc/logrotate.conf,各服务的独立配置放在 /etc/logrotate.d/ 目录下(debian/ubuntu 系)。每次运行时它会读取配置,判断日志文件是否满足"轮转条件",满足就把当前日志改名,并(可选)让服务重新打开日志句柄。
先看一下系统默认主配置:
# /etc/logrotate.conf weekly # 默认每周轮转一次 rotate 4 # 默认保留 4 份历史日志 create # 轮转后创建新日志文件,保持原属主权限 dateext # 使用日期作为轮转后缀,而非 .1 .2 .3 compress # 轮转后的旧日志使用 gzip 压缩 include /etc/logrotate.d # 载入服务级配置目录
最关键的一句话是 include /etc/logrotate.d,日常要新增自定义轮转规则,都是在这个目录下放一个独立配置文件,而不是改主配置。
三、编写自己的轮转规则
下面针对最常见的 Nginx 场景写一个配置 /etc/logrotate.d/nginx:
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily # 按天轮转
rotate 14 # 保留 14 天
maxsize 500M # 体积超过 500M 也强制轮转(更稳妥)
missingok # 日志文件不存在时不报错
notifempty # 空日志文件不轮转
compress # 旧日志 gzip 压缩
delaycompress # 压缩延迟一天,方便当天排错
dateext
dateformat -%Y%m%d
create 644 www-data www-data
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
要点解释:
daily+maxsize 500M:两种触发条件"或"关系,先到先轮转,避免大流量站点当天日志过大。postrotate:日志改名后,Nginx 还握着旧文件句柄继续写,必须发送 USR1 信号让 Nginx 重新打开新的 access.log,否则日志会一直写进已经改名(甚至被压缩)的旧文件里。delaycompress:压缩晚一天执行,防止误删刚轮转出来还没排查完的日志。
MySQL 的慢查询日志同理,轮转后需要重新刷新日志句柄:
# /etc/logrotate.d/mysql
/var/log/mysql/slow-query.log {
daily
rotate 7
compress
missingok
notifempty
create 640 mysql mysql
postrotate
mysqladmin -uroot -pXXX flush-logs 2>/dev/null || true
endscript
}
四、手动触发与调试
新配置不要等 cron 跑,手动验证:
# 模拟执行,只看会做什么,不真正轮转 logrotate -d /etc/logrotate.d/nginx # 强制执行(忽略 daily/monthly 等时间条件) logrotate -f /etc/logrotate.d/nginx # 查看某份日志是否真的转成功了 ls -lh /var/log/nginx/
执行后的目录效果:
-rw-r--r-- 1 www-data www-data 180M 2026-10-09 10:00 access.log -rw-r--r-- 1 www-data www-data 512M 2026-10-08 10:00 access.log-20261008 -rw-r--r-- 1 www-data www-data 480M 2026-10-07 10:00 access.log-20261007.gz
-d 是 dry-run,输出里 considering log /var/log/nginx/access.log 后如果出现 log needs rotating 说明条件已满足;出现 log does not need rotating 说明还没到时间点,这是排查"为什么没轮转"的第一招。
五、常见排错对照表
| 现象 | 原因 | 解决办法 |
|---|---|---|
| cron 每天跑但日志没被轮转 | 配置在非 root 用户目录、或文件属主无权限 | 确保配置在 /etc/logrotate.d/,用 logrotate -d 查看权限报错,检查 create 的属主设置 |
| 轮转后日志内容丢失/继续写旧文件 | postrotate 的 USR1 信号没发出去 | 确认 pid 文件路径,手动执行 kill -USR1 $(cat /var/run/nginx.pid) 验证 |
报错 error: skipping ... because parent directory has insecure permissions | 日志目录权限过宽(如 777) | 把目录权限收紧到 755,或配置加 su root adm 指定运行身份 |
| 轮转文件没有按日期命名 | 漏写 dateext / dateformat | 补上 dateext 并加 dateformat -%Y%m%d |
| 轮转时服务报 "No such file" | 日志文件刚被误删,服务重建失败 | 加 create 指令并配置正确属主;对 Nginx 再加 kill -USR1 |
另外注意:Ubuntu/Debian 的 logrotate 由 /etc/cron.daily/logrotate 触发,执行时间是每天 6:25 左右;如果你的应用对日志实时性要求高,可以自行加一个 crontab 更频繁触发:
# crontab -e */30 * * * * /usr/sbin/logrotate -f /etc/logrotate.d/nginx >/dev/null 2>&1
六、systemd journal 的单独治理
现在很多系统日志走 journald,它也有自己的轮转与上限配置(/etc/systemd/journald.conf):
# /etc/systemd/journald.conf SystemMaxUse=200M # journal 最多占用 200M SystemMaxFileSize=50M # 单份日志文件上限 MaxRetentionSec=7d # 保留 7 天 RuntimeMaxUse=50M # /run 内存中日志上限
# 修改后生效 systemctl restart systemd-journald # 立即强制清理 journalctl --vacuum-size=100M journalctl --vacuum-time=3d
七、总结
一套稳妥的日志轮转方案 = logrotate 按天/体积切割 + 保留合理份数 + 压缩归档 + postrotate 重开句柄 + 定期 dry-run 验证。配置完成后建议观察一周:确认 access.log 的日期后缀文件正常生成、压缩正常执行、服务无报错,再放心交给 cron。真正把磁盘告警消灭在日志层面,是 VPS 运维最值得投入的十分钟。




