Nginx 15 天日志轮转配置:logrotate、delaycompress 与 postrotate 报错排查
这篇文章给出一条可以直接落地的 Nginx 自定义日志轮转路径:让 logrotate 覆盖实际日志目录,每天轮转、保留约 15 天、压缩历史文件,并通过 Nginx USR1 让进程重新打开日志。重点不在“把文件改名”,而在于同时验证三件事:规则确实匹配了目标文件、压缩确实释放了空间、Nginx 确实不再写旧文件。
文中的主机名、目录名和数值都是泛化示例。把 /srv/nginx/log 替换成你的实际日志目录即可。

图中的 ① 是实际写入的活动日志,② 是匹配这批文件的规则,③ 和 ④ 分别对应改名/创建与压缩,⑤ 负责让 Nginx 放弃旧句柄,⑥ 用服务状态、inode 和 lsof 做交叉验证。
环境与前置条件
| 项目 | 示例 | 如何确认 |
|---|---|---|
| 操作系统 | Linux + systemd | cat /etc/os-release、systemctl --version |
| Nginx | 已运行的 master/worker 进程 | nginx -v、ps -o pid,user,cmd -C nginx |
| logrotate | 系统包提供的命令和 timer | logrotate --version、systemctl status logrotate.timer |
| 日志目录 | /srv/nginx/log |
sudo find /srv/nginx/log -maxdepth 1 -type f -name '*.log' -print |
| 权限 | 能读取 Nginx PID、写入 /etc/logrotate.d |
sudo -v |
如果日志目录不是发行版默认的 /var/log/nginx,先确认现有规则是否真的覆盖它。不要因为机器上有 /etc/logrotate.d/nginx,就假设所有 Nginx 日志都会轮转。
一、先界定“保留 15 天”到底保留什么
Nginx 日志轮转涉及三类文件:
- 当前正在写入的
.log文件; - 轮转后带日期后缀的历史文件;
- 历史文件进一步压缩得到的
.gz文件。
logrotate 是按文件轮转的工具,不会读取每一行的时间戳再裁剪内容。第一次轮转时,它会把当前活动文件整体改名,再创建新文件。因此,旧文件里即使包含很多天的内容,也会整体进入历史文件;“15 天”是历史文件的保留策略,不是按日志行做时间切片。
另一个容易混淆的点是:发行版默认规则常常只写 /var/log/nginx/*.log。如果 Nginx 实际把日志写到了自定义目录,默认规则不会自动跟随过去,磁盘就会持续增长。
二、写入一条明确的 logrotate 规则
先在保存规则的机器上备份同名文件;不存在时跳过备份:
RULE=/etc/logrotate.d/nginx-custom
if [ -f "$RULE" ]; then
sudo cp -a "$RULE" "$RULE.before.$(date +%Y%m%d%H%M%S)"
fi
sudo vim "$RULE"
写入下面这份配置。示例使用 worker 作为日志目录的组名,请按实际目录权限替换;不要把目录属主、组名和 Nginx 运行用户凭空改成别的值。
/srv/nginx/log/*.log {
daily
rotate 15
maxage 15
dateext
compress
delaycompress
missingok
notifempty
sharedscripts
su root worker
create 0644 nginx worker
postrotate
if [ -s /run/nginx.pid ]; then
kill -USR1 "$(cat /run/nginx.pid)"
elif [ -s /var/run/nginx.pid ]; then
kill -USR1 "$(cat /var/run/nginx.pid)"
fi
endscript
}
配置项怎么配合工作
| 配置项 | 作用 | 边界 |
|---|---|---|
daily |
每天检查并在需要时轮转 | 具体执行时间由系统 timer/cron 决定 |
rotate 15 |
最多保留 15 个轮转代次 | 不是严格的自然日保证 |
maxage 15 |
删除超过约 15 天的轮转文件 | 只在 logrotate 运行时检查 |
dateext |
文件名追加日期 | 便于人工和脚本按日期定位 |
compress |
历史文件压缩 | 释放空间的主要来源 |
delaycompress |
最近一次轮转的文件先不压缩 | 下一次轮转时才压缩,便于短期回看 |
missingok |
日志不存在时不报错 | 适合可选日志文件 |
notifempty |
空文件不轮转 | 避免制造无意义的历史文件 |
sharedscripts |
一组匹配文件只执行一次 postrotate |
避免多个日志文件重复发信号 |
su root worker |
以指定身份处理目录 | 必须与目录权限相容 |
create 0644 nginx worker |
创建下一代活动日志的权限和属主 | 不能只看文件权限,还要核对 Nginx 用户 |
USR1 是 Nginx 的“重新打开日志文件”信号。它不会重启 worker,也不需要中断业务连接;轮转后的关键是让 master 收到这个信号,而不是仅仅完成 mv/压缩。
postrotate 最容易踩的引号坑
配置文件里应该写普通的双引号:
kill -USR1 "$(cat /run/nginx.pid)"
不要把脚本生成器里的转义字符原样写进最终配置。如果最终文件里出现了反斜杠和引号,shell 可能把 PID 解析成带字面引号的参数,典型报错是:
kill: Illegal number: "\"<PID>\""
这类错误通常不会阻止前面的改名和压缩,所以只看目录里有没有 .gz 文件是不够的;必须单独检查 postrotate 的执行结果和旧文件句柄。
三、安全上线:先检查,再让定时器接管
1. 先做静态检查和 dry-run
在 Nginx 所在机器执行:
sudo nginx -t
sudo logrotate -d /etc/logrotate.d/nginx-custom
sudo systemctl status logrotate.timer --no-pager
期望看到:nginx -t 返回 syntax is ok;logrotate -d 返回 0,并且输出中列出目标日志文件。-d 是 dry-run,不会改名、压缩或删除文件。
典型的成功输出(版本和路径会因发行版不同而变化):
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
reading config file /etc/logrotate.d/nginx-custom
Handling 1 logs
2. 观察一次真实轮转
首次上线建议在维护窗口内手动触发一次,或等待下一次定时执行。手动触发会改名并创建新文件,不能当成普通的只读检查:
sudo logrotate -vf /etc/logrotate.d/nginx-custom
随后立刻查看服务日志和文件状态:
sudo journalctl -u logrotate.service -n 80 --no-pager
sudo find /srv/nginx/log -maxdepth 1 -type f -printf '%TY-%Tm-%Td %TH:%TM %s %p\n' | sort
如果只想验证配置,不要用 -f;先用 -d,把真实轮转留给定时器或已批准的维护窗口。
3. 验证“新文件在写、旧文件不再写”
sudo stat -c '%i %s %y %n' /srv/nginx/log/*.log
sudo lsof -nP -c nginx | grep '(deleted)' || true
活动文件应有新的 inode 和持续变化的大小;Nginx 不应继续持有已经轮转并删除的旧文件。lsof 没有输出只能说明当前没有匹配到已删除句柄,还要结合下一次请求和文件大小变化判断。

GIF 的 ①~⑤ 是同一条时序:先检查,再轮转和归档,随后发送 USR1,最后确认新文件在写、旧句柄清零。颜色变化只表示当前推进到哪一步,不代表额外的服务组件。
四、首次轮转为什么能释放很多空间
一次脱敏后的验证中,自定义日志目录从约 9 GB 降到约 0.66 GB,根盘使用率也随之下降。这个变化主要来自压缩,不是 maxage 立即删除:首次轮转只是把旧活动文件整体改名并压缩,真正达到年龄阈值的历史文件要到后续轮转才会被淘汰。
一个典型的首次轮转结果如下:
| 文件状态 | 轮转前 | 首次轮转后 |
|---|---|---|
活动 .log |
多个大文件持续增长 | 新建的较小活动文件 |
| 当天轮转文件 | 没有 | *.log-YYYYMMDD(因 delaycompress 暂不压缩) |
| 上一代轮转文件 | 没有 | *.log-YYYYMMDD.gz |
delaycompress 会让“当天的轮转文件”保持未压缩,下一次轮转才压缩它。因此刚执行完轮转时看到一批没有 .gz 后缀的日期文件,不代表规则失效。
根盘可用空间的变化不能全部归因于日志目录:同一时间其他目录也可能增减。可以高置信归因的是目标日志目录本身的容量下降;整盘变化要结合 du 的前后快照判断。
五、postrotate 报错时按链路排查
把问题拆成四段,能避免把“文件已改名”误判成“轮转完整成功”:
- 匹配:规则是否覆盖了真实日志路径;
- 文件操作:改名、创建、压缩是否成功;
- 信号:
postrotate是否成功读取 PID 并发送USR1; - 结果:Nginx 是否已经打开新文件,旧文件是否仍被持有。
如果日志里出现 Illegal number,先确认最终配置(不是生成模板)中的命令:
sudo sed -n '/postrotate/,/endscript/p' /etc/logrotate.d/nginx-custom
再做不改文件的 PID 解析检查:
PID_FILE=/run/nginx.pid
if [ ! -s "$PID_FILE" ] && [ -s /var/run/nginx.pid ]; then
PID_FILE=/var/run/nginx.pid
fi
PID=$(sudo cat "$PID_FILE")
case "$PID" in
''|*[!0-9]*) echo "invalid pid"; exit 1 ;;
esac
sudo kill -0 "$PID"
echo "pid is valid: $PID"
修复后重新执行 nginx -t 和 logrotate -d,再观察下一次真实定时执行:
sudo nginx -t
sudo journalctl -u logrotate.service --since '10 minutes ago' --no-pager
sudo systemctl is-active logrotate.timer
如果 systemd 仍显示 logrotate.service 为 failed,先区分“历史失败状态”和“本次仍失败”。一次成功的后续运行会产生新的日志;不要为了让状态看起来干净而跳过证据检查。确认修复后的运行成功后,可以按本机运维规范执行:
sudo systemctl reset-failed logrotate.service
六、如何确认 Nginx 已经重新打开日志
不要只看轮转文件是否出现,至少做下面三项交叉验证:
1. 看活动文件的起始时间
sudo head -n 1 /srv/nginx/log/access.log
sudo stat -c '%i %y %n' /srv/nginx/log/access.log
轮转后新文件的首条记录应接近轮转时间,inode 也应与轮转前不同。head 只读取一行,不会把大文件加载进内存。
2. 看 Nginx 打开的文件
sudo lsof -nP -c nginx | grep '/srv/nginx/log/'
输出应指向当前 .log 文件,而不是日期后缀文件或 (deleted) 文件。若仍指向旧文件,优先检查 PID 文件路径、Nginx master 权限和 postrotate 的错误日志。
3. 看轮转服务的退出结果
sudo journalctl -u logrotate.service -n 100 --no-pager
systemctl show -p ExecMainStatus -p Result logrotate.service
服务退出码为 0 只能证明 logrotate 进程成功结束;最终仍要结合文件句柄和下一次写入验证 Nginx 是否切换完成。
七、rotate 15 和 maxage 15 的边界
rotate 15是“保留多少代轮转文件”的上限,文件量变化时不等于严格 15 个自然日。maxage 15是“历史文件超过约 15 天后可删除”,删除动作发生在 logrotate 运行时,不是后台实时清理。- 每日轮转加
delaycompress时,刚轮转的那一代会暂时保持未压缩,所以实际观察到的保留窗口可能比 15 天多一个轮转周期。 - 如果日志一天都没有新内容,
notifempty会跳过轮转,年龄和文件数量不会按想象中的每天推进。
因此,容量治理不能只写一个数字就结束。建议同时监控:日志目录总大小、轮转文件数量、最近一次 logrotate.service 退出码、以及 Nginx 是否持有旧句柄。
快速参考
推荐配置骨架
/srv/nginx/log/*.log {
daily
rotate 15
maxage 15
dateext
compress
delaycompress
missingok
notifempty
sharedscripts
su root worker
create 0644 nginx worker
postrotate
if [ -s /run/nginx.pid ]; then
kill -USR1 "$(cat /run/nginx.pid)"
elif [ -s /var/run/nginx.pid ]; then
kill -USR1 "$(cat /var/run/nginx.pid)"
fi
endscript
}
上线检查顺序
sudo nginx -t
sudo logrotate -d /etc/logrotate.d/nginx-custom
sudo logrotate -vf /etc/logrotate.d/nginx-custom # 仅维护窗口使用
sudo journalctl -u logrotate.service -n 80 --no-pager
sudo lsof -nP -c nginx | grep '(deleted)' || true
三条易错规则
- 自定义日志目录必须单独写 glob,默认
/var/log/nginx规则不会自动覆盖它。 postrotate中的"$(cat PID_FILE)"是 shell 的正常写法;不要把生成器的转义反斜杠带进最终配置。- 看到
.gz只代表文件操作完成;只有“新活动文件 + Nginx 打开新文件 + 服务退出码正常”才算轮转链路闭环。

浙公网安备 33010602011771号