Nginx本身不会主动切割日志,当访问量增大时,access.log和error.log会持续膨胀,必须借助外部工具实现滚动。常见的做法是使用logrotate管理日志文件,但滚动过程中如果漏掉reopen信号,Nginx会继续持有旧文件的文件描述符,导致新日志写到已经改名的文件里。更棘手的是,一旦滚动策略失误,想恢复到某个时间点的日志记录,仅靠手动复制往往难以保证完整性。下面从滚动机制、失败场景和回滚方案三个层面拆解。

Nginx日志滚动的基本机制与logrotate配置
Nginx的日志写入依赖自身进程打开的文件描述符,它自身不会根据时间或大小自动分割日志。生产环境通常使用logrotate这套标准工具来管理日志文件。logrotate由系统cron定时触发,读取/etc/logrotate.conf以及/etc/logrotate.d/目录下的配置文件,按照预设条件执行滚动。对于Nginx日志,一个典型的配置会包含daily(每天滚动)、rotate 14(保留14份)、compress(压缩旧日志)、missingok(日志文件不存在时不报错)以及关键的postrotate脚本。
下面是一个常见的Nginx日志logrotate配置:
/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}这段配置中最核心的部分是postrotate块。logrotate完成文件改名或创建新文件后,会执行该块中的命令。kill -USR1信号专门用于通知Nginx重新打开日志文件:Nginx收到USR1信号后,会关闭旧的日志文件描述符并重新打开配置中指定的日志路径。如果省略这个信号,Nginx会继续写入已经被logrotate改名的文件,例如写入access.log.1,而新创建的access.log始终为空,后续滚动就会完全错乱。
要验证配置是否生效,可以手动执行logrotate -f /etc/logrotate.d/nginx强制触发一次滚动,然后观察/var/log/nginx/目录下文件的变化。使用logrotate -d加上配置文件路径可以进入调试模式,只输出将要执行的动作而不实际修改文件,这对排查配置文件语法错误和预测滚动结果非常有用。
日志滚动更新中的常见风险与回滚需求
日志滚动看似简单,实际执行时隐藏着不少坑。最常见的故障是postrotate脚本执行失败,原因可能是nginx.pid文件路径错误、进程用户权限不足、或者脚本中的if判断条件不成立导致kill信号根本没发出去。另一个常见问题是create参数设置的用户和组与Nginx worker进程的运行身份不一致,新日志文件创建后Nginx没有写入权限,日志直接停止记录。此外,如果使用了sharedscripts而多个日志文件同时滚动,信号只发一次,但某些Nginx配置下需要更精细的处理,也会造成日志写入异常。
当发现日志滚动出现问题时,回滚记录的需求就浮现出来。例如,误删了尚未压缩的access.log.1,或者需要审计三天前的访问日志却发现对应的归档文件已经损坏,此时只能依赖事前的备份。如果没有任何回滚准备,旧日志一旦被压缩删除,恢复成本极高,甚至无法恢复。因此,在滚动策略之外设计一套独立的备份与回滚机制,是保障日志可追溯性的关键。
一个典型的失败场景可以通过手动模拟:将logrotate配置中的postrotate块注释掉,执行logrotate -f,然后继续访问Nginx。用ls -l查看/var/log/nginx/目录,会发现access.log的大小不再增长,而access.log.1的大小持续增加。此时如果要回滚,就需要把access.log.1中的数据合并回access.log,并且重新发送USR1信号让Nginx切换文件描述符,操作繁琐且容易出错。
构建可回滚的Nginx日志归档与恢复方案
与其在故障发生后被动补救,不如在每次滚动前主动创建快照。思路是在logrotate的prerotate阶段调用自定义备份脚本,将当前的access.log和error.log复制到独立的备份目录,并使用日期时间戳命名,保留多份历史快照。这样即使logrotate的滚动结果不理想,也能从备份目录中恢复到任意时间点的日志状态。备份目录建议放在不同磁盘分区,防止同一块盘故障导致日志和备份同时丢失。
以下是一个完整的备份与恢复脚本示例,它遍历所有Nginx日志文件,将当前内容打包压缩,并提供基于日期参数的恢复功能:
#!/bin/bash
# nginx_log_backup_restore.sh
LOG_DIR="/var/log/nginx"
BACKUP_DIR="/var/backups/nginx_logs"
ACTION="$1"
RESTORE_DATE="$2"
backup_logs() {
DATE_STAMP=$(date +%Y%m%d%H%M%S)
mkdir -p "$BACKUP_DIR"
for logfile in "$LOG_DIR"/*.log; do
base=$(basename "$logfile")
tar -czf "$BACKUP_DIR/${base}.${DATE_STAMP}.tar.gz" -C "$LOG_DIR" "$base"
done
echo "Backup completed at $DATE_STAMP"
}
restore_logs() {
if [ -z "$RESTORE_DATE" ]; then
echo "Usage: $0 restore YYYYMMDD"
exit 1
fi
for logfile in "$LOG_DIR"/*.log; do
base=$(basename "$logfile")
backup_file="$BACKUP_DIR/${base}.${RESTORE_DATE}*.tar.gz"
if ls $backup_file 1> /dev/null 2>&1; then
tar -xzf $backup_file -C "$LOG_DIR"
echo "Restored $base from $backup_file"
else
echo "No backup found for $base at $RESTORE_DATE"
fi
done
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
}
case "$ACTION" in
backup)
backup_logs
;;
restore)
restore_logs
;;
*)
echo "Usage: $0 {backup|restore YYYYMMDD}"
exit 1
;;
esac脚本中backup_logs函数使用tar将每个日志文件压缩成带时间戳的归档,restore_logs函数根据传入的日期前缀匹配备份文件并解压回原目录。恢复完成后自动发送USR1信号,让Nginx重新打开恢复后的日志文件。注意恢复操作会覆盖当前日志,如果不想覆盖,可以在解压时添加--keep-old-files参数或者先手动备份当前日志。
将备份脚本与logrotate整合,只需在logrotate配置的prerotate和postrotate之间插入调用。例如在配置文件中加入prerotate块,执行/path/to/nginx_log_backup_restore.sh backup,这样每次滚动前都会自动生成快照。建议将备份保留策略与logrotate的rotate值保持一致,比如保留30份快照,便于回滚到一个月内的任意时间点。恢复完成后还要检查日志文件的所有者和权限,确保Nginx worker进程可写,否则日志会再次停止记录。