Linux 日志轮转 logrotate 进阶实战:配置、压缩与排错指南
很多 VPS 运维新手都会遇到同一个问题:运行了几个月之后,磁盘空间被日志文件悄悄吃光。尤其是 Nginx、MySQL、Java 应用这类高频写日志的程序,单个日志文件动辄几个 GB,不仅占磁盘,还会拖慢日志分析、影响系统性能。这篇文章从 logrotate 的核心机制讲起,配合完整的配置示例和常见排错方法,帮助你彻底掌握 Linux 下的日志轮转。
一、为什么需要日志轮转
日志文件如果不加控制,会一直无限增长。以 Nginx 默认的 access.log 为例,在日均几十万请求的站点上,一天就能产生几百 MB 的日志。日志无限增长会带来三个直接问题:
- 磁盘耗尽:根分区被写满后,服务进程可能直接崩溃,甚至系统无法写入任何文件。
- 日志无法读取:单个日志文件过大,用 vim、tail 查看都非常吃力,检索效率急剧下降。
- 影响性能:持续追加写入大文件会产生大量磁盘 IO,拖慢同盘的其他服务。
logrotate 是 Linux 下最主流的日志轮转工具,几乎所有发行版都默认安装(属于 logrotate 软件包)。它通过 cron 定时任务每日执行一次,按照配置对日志进行分割、压缩、删除和轮换,让日志始终保持在可控范围内。
先确认你的系统是否安装了 logrotate:
# Debian/Ubuntu
dpkg -l | grep logrotate
# CentOS/RHEL
rpm -qa | grep logrotate
# 如果未安装
apt install -y logrotate # Debian/Ubuntu
yum install -y logrotate # CentOS 7
dnf install -y logrotate # CentOS 8+
安装后,系统会在 /etc/cron.daily/ 下自动放置 logrotate 定时任务,每天凌晨自动执行一次:
# cat /etc/cron.daily/logrotate
#!/bin/sh
/usr/sbin/logrotate /etc/logrotate.conf
EXITVALUE=$?
if [ $EXITVALUE != 0 ]; then
/usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
fi
exit 0
二、logrotate 核心机制与配置语法
logrotate 的主配置文件是 /etc/logrotate.conf,它通常只包含全局默认设置,并通过 include 指令引入 /etc/logrotate.d/ 目录下的所有子配置文件:
# cat /etc/logrotate.conf
weekly # 默认每周轮转一次
rotate 4 # 保留 4 份旧日志
create # 轮转后创建新日志文件(默认权限 0644 root root)
dateext # 用日期作为轮转后缀,如 access.log-20261025
compress # 轮转后的旧日志使用 gzip 压缩
include /etc/logrotate.d # 引入 /etc/logrotate.d 下的所有配置
/etc/logrotate.d/ 下每个文件描述一组日志的轮转规则,基本语法为:
日志文件路径 {
指令1
指令2
...
}
下面是最常用的一组指令及其含义:
/var/log/example/*.log {
daily # 每天轮转一次(还可写 weekly / monthly)
rotate 7 # 保留 7 份旧日志,超出则删除最旧的
compress # 旧日志用 gzip 压缩
delaycompress # 与 compress 配合:本轮不压缩刚轮转的日志,下次再压
missingok # 日志文件不存在时不报错
notifempty # 日志文件为空时不轮转
create 0640 www-data www-data # 轮转后新建日志的权限和属主
dateext # 使用日期后缀(默认),可用 dateformat 自定义格式
sharedscripts # 多个日志匹配时,postrotate 脚本只执行一次
postrotate # 轮转后执行的命令
kill -USR1 `cat /var/run/nginx.pid`
endscript
}
三、实战:Nginx 日志轮转配置
Nginx 官方建议用 kill -USR1 信号让 Nginx 重新打开日志文件句柄,否则轮转后 Nginx 仍会继续往被改名(或删除)的旧文件里写日志。下面是一份完整的 Nginx 日志轮转配置:
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
创建后,用 debug 模式验证配置是否正确(不会实际执行轮转):
logrotate -d /etc/logrotate.d/nginx
输出中如果出现 rotating pattern 和 considering log /var/log/nginx/access.log 等内容,说明配置被正确加载。确认无误后手动执行一次:
logrotate -f /etc/logrotate.d/nginx
ls -lh /var/log/nginx/
# 输出示例
# access.log
# access.log-20261025.gz
# error.log
# error.log-20261025.gz
如果站点访问量大,daily 轮转一次可能还不够,可以考虑使用 hourly 模式。做法是把脚本放到 /etc/cron.hourly/ 下,并开启 hourly 指令:
# /etc/cron.hourly/logrotate-hourly
#!/bin/sh
/usr/sbin/logrotate /etc/logrotate.hourly.conf
exit 0
# /etc/logrotate.hourly.conf
/var/log/nginx/*.log {
hourly
rotate 24
...
}
注意 hourly 轮转需要自己创建 cron 脚本,因为系统默认只提供 daily 任务。
四、实战:按大小轮转与自定义应用日志
有些场景更适合按文件大小轮转,比如磁盘空间紧张、或者日志写入速度极不规律。size 指令可以单独使用,也可以与 daily 配合(两者都满足才轮转):
# /etc/logrotate.d/mysql
/var/log/mysql/*.log {
size 100M # 超过 100MB 才轮转
rotate 10
compress
missingok
create 0640 mysql adm
postrotate
test -x /usr/bin/mysqldump || exit 0
mysqldump --flush-logs --master-data=2 --single-transaction > /dev/null
endscript
}
对于 Java / Python 等自研应用,日志通常写在应用自己的目录里,配置方式完全相同。假设你的应用把日志写到 /opt/myapp/logs/app.log,并且由 systemd 托管:
# /etc/logrotate.d/myapp
/opt/myapp/logs/*.log {
daily
rotate 30
compress
dateext
dateformat -%Y%m%d # 自定义日期格式
missingok
notifempty
create 0644 app app
sharedscripts
postrotate
systemctl reload myapp.service >/dev/null 2>&1 || true
endscript
}
这里的关键点是:应用必须支持"重开日志句柄"(如 log4j2 的 RollingFile + 定时检查,或 Python logging 的 TimedRotatingFileHandler),否则轮转后应用仍然写旧文件。如果应用不支持优雅重开,只能用 copytruncate:
# copytruncate 方案:先复制再截断,应用无感知
/opt/myapp/logs/app.log {
daily
rotate 7
compress
copytruncate # 复制原文件后立即 truncate,应用不需要重开句柄
missingok
}
copytruncate 的缺点是在复制与截断之间可能丢失极少量日志,且复制大文件时会有短暂 IO 压力,适合"应用无法配合"的兜底场景。
五、排错:常见问题与诊断命令
logrotate 的排错基本围绕"不轮转、轮转报错、轮转后服务异常"三类问题展开。下面列出最常见的几种情况和对应解法。
5.1 日志根本没有轮转
先用 verbose 模式手动跑一次,观察具体原因:
logrotate -v /etc/logrotate.conf
常见原因与对策:
- notifempty 生效:日志文件为空时跳过轮转,属正常行为。
- 状态文件记录未到轮转时间:logrotate 把上次轮转时间记录在 /var/lib/logrotate/logrotate.status,daily 轮转在同一天内重复执行会被跳过。可以用
-f强制轮转。 - 权限不足:logrotate 默认以 root 运行,但如果是普通用户调用,读不到日志文件会报 permission denied。
- 路径写错:配置里的 glob 匹配不到任何文件,用 debug 模式确认。
5.2 轮转后服务不写日志或写错文件
这是 Nginx/MySQL 等服务的经典问题。排查方法:
# 查看进程当前打开的日志文件句柄
ls -l /proc/$(pidof nginx | awk '{print $1}')/fd | grep log
如果句柄仍然指向被轮转的旧文件(如 access.log-20261025.gz),说明 postrotate 里的信号没有生效。解决办法:
# 手动向 Nginx 发送重开句柄信号
kill -USR1 $(cat /var/run/nginx.pid)
并检查 postrotate 脚本里的 pid 文件路径是否与 Nginx 实际配置一致(有些编译版 Nginx 把 pid 放在 /usr/local/nginx/logs/nginx.pid)。
5.3 磁盘空间不降反升
常见原因是 delaycompress 生效期间,旧日志以未压缩状态保留了一轮;如果日志量大,建议去掉 delaycompress,或者提高 compresscmd 的压缩级别:
# 使用 xz 获得更高压缩比
compress
compresscmd /usr/bin/xz
compressext .xz
5.4 日志权限变成 root 导致应用无法写入
create 指令指定的属主/权限不对时,应用会报 permission denied。排查:
# 查看轮转后的文件权限
ls -l /var/log/nginx/access.log
# 用 create 指令纠正属主
create 0640 nginx adm
5.5 查看 logrotate 执行日志
logrotate 自身不写日志文件,但可以通过 postrotate 或系统日志确认执行情况:
# 查看 cron 执行记录
journalctl -u logrotate.timer --since today
# 或查看系统日志中的 ALERT 信息
grep logrotate /var/log/syslog
systemd 发行版还可以通过 systemctl status logrotate.timer 查看定时任务是否被正确触发。
六、进阶:轮转状态的监控与清理策略
日志轮转不是配置完就一劳永逸,建议把轮转结果纳入监控。下面给出一段简单的健康检查脚本,可以配合 cron 每天执行:
#!/bin/bash
# /usr/local/bin/check_logrotate.sh
LOG_DIRS="/var/log/nginx /var/log/mysql"
WARN_SIZE=500
for dir in $LOG_DIRS; do
for f in $(find "$dir" -name "*.log" -size +${WARN_SIZE}M); do
echo "[WARN] $f size over ${WARN_SIZE}M, rotate may be failing"
done
done
同时,需要结合磁盘实际情况设计保留策略:日志保留份数 rotate N × 轮转周期,要保证总占用不超过磁盘容量的合理比例(建议日志盘使用率不超过 60%)。也可以把日志目录单独挂载到独立分区,避免根分区被写满:
# 将 /var/log 挂载到独立磁盘(示例,需根据实际设备调整)
mkfs.ext4 /dev/sdb1
mkdir -p /mnt/log
mount /dev/sdb1 /mnt/log
rsync -a /var/log/ /mnt/log/
rm -rf /var/log/*
mount --bind /mnt/log /var/log
# 写入 /etc/fstab 持久化
七、小结
logrotate 是 Linux 运维中最基础也最实用的工具之一。掌握它的关键在于理解几个核心概念:轮转周期(daily/weekly/size)、保留份数(rotate)、压缩(compress)、以及服务句柄重开(postrotate)。日常运维中,建议把常用服务的 logrotate 配置做成标准模板,配合 debug 和 force 参数快速排错,并把日志空间使用纳入监控,这样日志就永远不会成为服务器的隐患。
如果你在实际配置中遇到其他问题,欢迎在评论区留言交流。



