Nginx 15 天日志轮转配置:logrotate、delaycompress 与 postrotate 报错排查

这篇文章给出一条可以直接落地的 Nginx 自定义日志轮转路径:让 logrotate 覆盖实际日志目录,每天轮转、保留约 15 天、压缩历史文件,并通过 Nginx USR1 让进程重新打开日志。重点不在“把文件改名”,而在于同时验证三件事:规则确实匹配了目标文件、压缩确实释放了空间、Nginx 确实不再写旧文件。

文中的主机名、目录名和数值都是泛化示例。把 /srv/nginx/log 替换成你的实际日志目录即可。

Nginx 日志轮转总览:日志文件、logrotate 与 Nginx USR1

图中的 ① 是实际写入的活动日志,② 是匹配这批文件的规则,③ 和 ④ 分别对应改名/创建与压缩,⑤ 负责让 Nginx 放弃旧句柄,⑥ 用服务状态、inode 和 lsof 做交叉验证。

环境与前置条件

项目 示例 如何确认
操作系统 Linux + systemd cat /etc/os-releasesystemctl --version
Nginx 已运行的 master/worker 进程 nginx -vps -o pid,user,cmd -C nginx
logrotate 系统包提供的命令和 timer logrotate --versionsystemctl 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 日志轮转涉及三类文件:

  1. 当前正在写入的 .log 文件;
  2. 轮转后带日期后缀的历史文件;
  3. 历史文件进一步压缩得到的 .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 没有输出只能说明当前没有匹配到已删除句柄,还要结合下一次请求和文件大小变化判断。

轮转时序:检查、轮转、压缩、发送 USR1、验证

GIF 的 ①~⑤ 是同一条时序:先检查,再轮转和归档,随后发送 USR1,最后确认新文件在写、旧句柄清零。颜色变化只表示当前推进到哪一步,不代表额外的服务组件。

四、首次轮转为什么能释放很多空间

一次脱敏后的验证中,自定义日志目录从约 9 GB 降到约 0.66 GB,根盘使用率也随之下降。这个变化主要来自压缩,不是 maxage 立即删除:首次轮转只是把旧活动文件整体改名并压缩,真正达到年龄阈值的历史文件要到后续轮转才会被淘汰。

一个典型的首次轮转结果如下:

文件状态 轮转前 首次轮转后
活动 .log 多个大文件持续增长 新建的较小活动文件
当天轮转文件 没有 *.log-YYYYMMDD(因 delaycompress 暂不压缩)
上一代轮转文件 没有 *.log-YYYYMMDD.gz

delaycompress 会让“当天的轮转文件”保持未压缩,下一次轮转才压缩它。因此刚执行完轮转时看到一批没有 .gz 后缀的日期文件,不代表规则失效。

根盘可用空间的变化不能全部归因于日志目录:同一时间其他目录也可能增减。可以高置信归因的是目标日志目录本身的容量下降;整盘变化要结合 du 的前后快照判断。

五、postrotate 报错时按链路排查

把问题拆成四段,能避免把“文件已改名”误判成“轮转完整成功”:

  1. 匹配:规则是否覆盖了真实日志路径;
  2. 文件操作:改名、创建、压缩是否成功;
  3. 信号postrotate 是否成功读取 PID 并发送 USR1
  4. 结果: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 -tlogrotate -d,再观察下一次真实定时执行:

sudo nginx -t
sudo journalctl -u logrotate.service --since '10 minutes ago' --no-pager
sudo systemctl is-active logrotate.timer

如果 systemd 仍显示 logrotate.servicefailed,先区分“历史失败状态”和“本次仍失败”。一次成功的后续运行会产生新的日志;不要为了让状态看起来干净而跳过证据检查。确认修复后的运行成功后,可以按本机运维规范执行:

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 15maxage 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

三条易错规则

  1. 自定义日志目录必须单独写 glob,默认 /var/log/nginx 规则不会自动覆盖它。
  2. postrotate 中的 "$(cat PID_FILE)" 是 shell 的正常写法;不要把生成器的转义反斜杠带进最终配置。
  3. 看到 .gz 只代表文件操作完成;只有“新活动文件 + Nginx 打开新文件 + 服务退出码正常”才算轮转链路闭环。
posted @ 2026-08-31 11:40  Hello_worlds  阅读(21)  评论(0)    收藏  举报