关键在于日志文件句柄的持有与切换机制,而非升级动作本身;必须通过usr1信号触发reopen,并确保路径绝对、权限正确、pid路径一致,升级后强制reopen并验证时间戳连续性。

服务升级时日志不中断、不丢失,关键不是靠“升级动作”本身,而是靠日志文件句柄的持有与切换机制。Nginx 的日志写入依赖工作进程打开的文件描述符(fd),只要这个 fd 没被关闭,日志就持续写入——哪怕你重命名了文件、替换了二进制、甚至 reload 配置。真正影响日志连续性的,是是否在恰当节点触发 USR1 信号(即 nginx -s reopen)。
升级前必须确保日志路径和权限稳定
升级过程(尤其是二进制替换或配置变更)容易隐性破坏日志写入条件:
- 新版本 nginx 二进制启动后,若配置中
access_log或error_log使用相对路径(如logs/access.log),而工作进程当前目录(cwd)与旧版不一致,可能导致日志写入失败或错位;务必统一使用绝对路径,例如/var/log/nginx/access.log。 - 新主进程以不同用户身份启动(如从
root改为nginx),但日志目录仍属旧用户且无写权限,reopen会静默失败;升级前确认:ls -ld /var/log/nginx和stat /var/log/nginx/access.log,确保目标用户有写权限。 - 如果升级伴随配置重写,检查
pid文件路径是否变更(如从/var/run/nginx.pid改为/run/nginx.pid),否则reopen无法准确定位主进程。
平滑升级期间的日志接管时机
Nginx 平滑升级(USR2 + WINCH + QUIT)本身不触发日志 reopen,旧工作进程继续写原日志文件,新工作进程默认也写同一路径——这看似连续,实则存在风险:
- 若你在升级中途手动切分日志(比如 cron 触发 logrotate),而只向旧主进程发 USR1,新主进程可能仍在写旧文件句柄,造成双写;
- 更稳妥的做法是:在新主进程完全接管流量、旧主进程退出前,对新主进程单独执行一次
nginx -s reopen(需用其 PID 或确保信号发给当前活跃主进程); - 验证方式:升级完成后,执行
lsof -p $(cat /var/run/nginx.pid) | grep log,确认所有工作进程打开的是最新命名的日志文件(而非 .1、.2 或已重命名的旧名)。
推荐组合策略:logrotate + 升级后强制 reopen
不要依赖升级动作自动处理日志,而是把日志轮转和升级解耦,用外部工具兜底:
- 日常轮转仍走
logrotate,配置中postrotate调用nginx -s reopen,并加判断:if [ -f /proc/$(cat /var/run/nginx.pid)/exe ]; then nginx -s reopen; fi,避免信号发给已退出进程; - 升级操作完成后(确认
systemctl status nginx显示 active,且旧进程 PID 不再存在),立即手动执行一次nginx -s reopen,强制所有现存工作进程刷新日志句柄; - 若使用容器化部署(如 Docker),在 entrypoint 或 health check 后加入 reopen 命令,确保每次启动/重启都完成日志初始化。
验证日志连续性最直接的方法
别只看文件名是否更新,要确认内容是否无缝衔接:
- 切割前最后一条日志时间戳(如
tail -1 /var/log/nginx/access.log | awk '{print $4}'); - 切割后新文件第一条日志时间戳(
head -1 /var/log/nginx/access.log.20260820); - 两者应相差 ≤1 秒,且无请求 gap(可比对 access 日志中的 request_time 或 upstream_response_time 分布);
- 极端情况下,用
strace -p $(pgrep -f 'nginx: worker') -e write -s 1024 2>&1 | grep log实时观察写入路径,确认 fd 指向正确文件。











