USR1信号能安全切换日志文件,因Nginx worker通过文件描述符(fd)写入日志,与文件名无关;mv仅改名不改变inode,发送USR1后主进程通知所有worker关闭旧fd并按配置路径重新open新文件,实现原子切换。

USR1信号为什么能安全切换日志文件
Nginx worker 进程在启动时用 open() 拿到日志文件的文件描述符(fd),后续所有写入都基于这个 fd,和文件名完全无关。所以你 mv /var/log/nginx/access.log /var/log/nginx/access.log-20260908 之后,Nginx 仍在往同一个 inode 写——只要没发 USR1,它压根不知道文件被改名了。
发送 USR1 后,Nginx 主进程通知所有 worker:关闭当前 fd,再按 access_log 配置项里写的路径(比如 /var/log/nginx/access.log)重新 open() 一次。新文件由此诞生,旧文件可放心归档或压缩。
验证是否生效:切割后立刻执行 ls -li /var/log/nginx/access*,对比前后两个文件的 inode 号——如果一致,说明还没切成功;如果不一致,且新 access.log 大小从 0 开始增长,就对了。
手动脚本里必须检查 pid 文件是否存在
kill -USR1 必须作用于 Nginx 主进程,而主进程 pid 存在 /var/run/nginx.pid 或 /usr/local/nginx/logs/nginx.pid,取决于你的安装方式。直接硬写路径会失败。
脚本中务必加判断,否则 cron 执行时若 pid 文件缺失,整条命令静默失败,日志就堆在旧文件里不动了:
if [ -f /var/run/nginx.pid ]; then kill -USR1 $(cat /var/run/nginx.pid) else echo "nginx.pid not found, skip reopen" >&2 exit 1 fi
常见坑点:
-
nginx.conf里没配pid指令,导致根本没生成 pid 文件 - 权限问题:
cat /var/run/nginx.pid被拒绝,因为该文件属主是root,但 cron 以www-data运行 - 路径写错,比如把
/var/run/nginx.pid写成/var/run/nginx.pid.lock
mv 和 kill -USR1 的顺序不能颠倒
必须先 mv,再 kill -USR1。反过来会导致 Nginx 在重开日志时发现目标路径已被占用(比如旧文件还叫 access.log),可能报错或跳过创建新文件。
更稳妥的做法是带时间戳重命名 + 原子操作:
logs_path="/var/log/nginx"
mv "${logs_path}/access.log" "${logs_path}/access.log.$(date +\%Y\%m\%d_\%H\%M\%S)" 2>/dev/null || true
kill -USR1 $(cat /var/run/nginx.pid)
注意:
- 不要用
cp+truncate或copytruncate,这些操作会丢日志 - 绝对不要
rm access.log—— 文件删除后 inode 仍被进程持有,磁盘空间不释放,直到 Nginx 重启 - 如果日志路径含变量(如
access.log.$time_iso8601),各 worker 可能生成不同文件名,导致切割混乱,应禁用
nginx -s reopen 和 kill -USR1 是等价的
nginx -s reopen 底层就是向主进程发 USR1 信号,二者行为完全一致。区别只在调用方式:
-
nginx -s reopen会自动读取配置里的pid路径,省去手动cat步骤,但要求nginx命令在$PATH中且有足够权限 -
kill -USR1 $(cat ...)更底层、更可控,适合集成进复杂脚本
生产环境建议统一用一种方式,避免混用造成排查困难。如果你的 nginx 是源码编译安装在 /usr/local/nginx,记得把 /usr/local/nginx/sbin 加进 PATH,否则 nginx -s reopen 会报 command not found。
最易被忽略的一点:USR1 不会 reload 配置,也不会中断连接,但它依赖主进程存活。如果主进程已挂(比如被误杀),发信号无效,worker 会继续往旧文件写——所以定期检查 ps aux | grep nginx: master 比写脚本更重要。


















