Nginx 日志轮转后不再写入?文件句柄、属主与 reopen 排查

Nginx 日志轮转后,新 access.log 一直为空,旧日志却继续增长,往往需要核对进程仍持有的文件句柄。本文围绕日志路径、inode、master PID、reopen 信号和文件权限展开排查,说明 logrotate 的 postrotate、delaycompress 与 copytruncate 边界,并给出手动恢复、自动轮转验收及回滚注意事项。命令按常见 Linux 环境举例,需结合实际安装方式调整。
Nginx 日志轮转后不再写入?文件句柄、属主与 reopen 排查

网站访问正常,access.log 却从凌晨轮转后一直是空文件;旁边的 access.log.1 仍在增长。有时连旧日志都看不到,磁盘占用却迟迟不降。这两种现象都值得检查 Nginx 当前打开的文件句柄。

Linux 进程打开文件后,重命名路径不会自动让进程改写同名的新文件。Nginx 官方给出的轮转流程是先重命名日志,再向 master 发送 USR1,让 master 和 worker 重新打开日志。[1] 单独创建一个新的空文件,无法完成整个切换。

本文适用于 Linux 上将日志写入普通文件的 Nginx。容器标准输出、syslog、远程日志采集,以及带变量的动态日志路径需要结合各自链路判断。下文路径均为示例,未在读者的生产环境执行。

确认停写的是哪个站点、哪个文件

tail -f 默认跟随文件描述符。轮转前启动的终端可能还在看旧文件,因此终端没有新输出,不能单独证明日志停写。GNU tail 可用 tail -F 按文件名跟随并重试;也可以重新打开当前日志核对。[5]

使用实际运行的 Nginx 二进制检查配置。若服务带有自定义 -p 或 -c 参数,检查命令也要使用相同参数。

sudo nginx -T 2>&1 | less
sudo ls -li /var/log/nginx/access.log /var/log/nginx/access.log.1
sudo stat /var/log/nginx/access.log /var/log/nginx/access.log.1

nginx -T 会测试配置并输出配置文件。[2] 输出可能包含内部域名和敏感配置,分享时需要脱敏。

在输出中找到目标域名对应的 server、请求命中的 location 和 access_log。检查有无独立日志路径、access_log off、条件日志,以及 buffer、flush 参数。访问日志缓冲会延迟落盘;带变量的日志路径还可能受 open_log_file_cache 影响。[3]

如果请求经过 CDN,缓存命中时可能不会到源站。用自己控制的只读页面做一条可识别请求,并确认请求确实到达这台 Nginx。多台源站时,要记录负载均衡实际选择的实例。不要为了造日志调用支付、删除或其他有写入副作用的接口。

看进程究竟还在写哪个 inode

文件名只是一层路径映射,ls -li 展示的 inode 可以帮助区分当前文件与轮转文件。安装了 lsof 的环境可以检查 Nginx 打开的日志。

sudo lsof -nP -c nginx | grep '/var/log/nginx/'
sudo lsof -nP +L1 | grep nginx

第一条命令适用于示例日志目录,实际目录不同需要替换。第二条查找链接数小于 1 的已打开文件,其中可能出现标记为 (deleted) 的日志。大机器上全局 lsof 可能较慢,可以针对已经确认的 PID 缩小范围。

没有 lsof 时,先列出进程,再检查具体 worker 的文件描述符。下面的 1234 必须替换为实际 PID。

ps -eo pid,ppid,user,args | grep '[n]ginx'
sudo ls -l /proc/1234/fd

旧文件持续增长、新文件保持为空,通常应查轮转后的 reopen 是否执行。句柄指向已删除文件,则说明进程仍持有该文件;继续删除其他日志不能解决这个句柄的占用。

如果句柄已经指向新日志,接下来应查请求是否到达、日志过滤条件和缓冲配置。轮转故障与“没有符合条件的请求”需要分开处理。

核对 reopen 发给了哪个 Nginx 实例

nginx -s reopen 用于重新打开日志,nginx -s reload 用于重新加载配置。[2] 排查单纯轮转问题时,专门的 reopen 操作能减少无关变更。

同一主机可能同时存在系统包安装、源码安装或面板管理的多个 Nginx。向默认 PID 文件中的进程发信号,未必会触及目标实例。可以先查看服务定义与进程信息。

sudo systemctl cat nginx
sudo systemctl show nginx -p MainPID -p ExecStart -p ExecReload
ps -eo pid,ppid,user,args | grep '[n]ginx'

这些 systemd 命令只适用于服务名确实为 nginx 的环境。面板服务、容器以及其他名称的 unit 应使用对应管理方式。确认二进制、配置前缀和 master PID 后,再选择一种发送方式。

# 适用于已确认使用默认二进制、配置及 PID 路径的实例
sudo nginx -s reopen

# 或向已核实的目标 master PID 发送信号;1234 为占位 PID
sudo kill -USR1 1234

两种方式不需要连续执行。不能把占位 PID 直接复制到生产环境,也不建议用不加区分的 killall 对多个实例一起发信号。

命令成功退出只表示发送环节没有报错。随后仍要检查错误日志、文件句柄和新的访问记录。若服务使用独立安装路径,用它自己的二进制及相同启动参数执行 reopen。

新文件权限正确,reopen 才能完成

轮转脚本中的 create 决定新文件最初的权限和属主。Nginx master 收到 USR1 后会重新打开当前日志,并按官方描述将文件属主调整为 worker 的非特权用户;这仍依赖 master 的实际权限和文件系统条件。[1] 非 root 服务、只读挂载、受限容器或安全策略都可能影响操作。

检查目标文件及父目录。www-data、nginx 等用户名不能跨发行版直接照搬。

sudo namei -l /var/log/nginx/access.log
sudo ls -ld /var/log/nginx
sudo ls -l /var/log/nginx/access.log
sudo journalctl -u nginx --since '30 minutes ago' --no-pager

journal 不一定收集 Nginx 的文件错误日志,还要查看有效配置中的 error_log 路径。重点关注 reopen 时刻的 Permission denied、Read-only file system 和 No space left on device。

日志目录需要允许相关进程遍历。开启 SELinux 的机器还应核对文件安全上下文与审计记录;不能用 chmod 777 或关闭 SELinux 代替定位。磁盘可用空间与 inode 也应一起检查。

df -h /var/log/nginx
df -i /var/log/nginx

修复权限时仅调整已经核实的文件或目录,并记录原权限以便回滚。对整个 /var/log 递归修改属主,可能影响其他服务及日志采集程序。

把轮转规则与发送信号的步骤一起检查

logrotate 负责轮转,Nginx 负责切换打开的文件。轮转后新日志停写时,需要连同 postrotate 脚本检查,尤其是升级软件、替换安装方式或迁移目录之后。

下面是用于阅读配置的示意,不能覆盖发行版已经提供的规则。www-data、adm 和 /usr/sbin/nginx 必须按运行环境替换;自定义配置实例还需同步启动参数。

/var/log/nginx/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    create 0640 www-data adm
    sharedscripts
    postrotate
        /usr/sbin/nginx -s reopen
    endscript
}

sharedscripts 让该段匹配到多个文件时,轮转脚本按这一段执行一次;如果没有文件需要轮转,脚本不会运行。postrotate 位于轮转后、压缩前,delaycompress 则把上一份轮转日志的压缩推迟到下一轮。[4]

delaycompress 只能提供缓冲时间。reopen 持续失败时,旧句柄不会自行切到新文件。还要检查是否有两份规则同时匹配同一个日志,或者面板脚本与系统 logrotate 重复轮转。

调试前保存配置副本,并检查系统如何调度 logrotate。以下命令适用于常见路径与 systemd 环境。

sudo logrotate -d /etc/logrotate.conf
sudo systemctl status logrotate.timer logrotate.service --no-pager
sudo journalctl -u logrotate.service --since '2 days ago' --no-pager

-d 为调试模式,不修改日志和状态文件。[4] 只检查 /etc/logrotate.d/nginx 时,可能缺少全局配置上下文,因此还应检查完整入口。部分系统通过 cron 调度,unit 不存在不能直接判定轮转失效。

logrotate -f 会强制执行轮转,不是无副作用的语法检查。只有在保留策略、备份、脚本和目标实例均已核实后,才适合在维护窗口做受控验证。频繁强制轮转可能提前淘汰历史日志。

copytruncate 有丢日志窗口

copytruncate 会复制日志后截断原文件,让原 inode 保持不变。它可用于无法通知重开日志的程序,但复制与截断之间存在时间窗口,期间写入的部分日志可能丢失,logrotate 官方手册明确提示了这一点。[4]

Nginx 本身支持 reopen,通常可以沿用“重命名后通知重开”的轮转方式。排查时临时切到 copytruncate,容易掩盖 PID 路径或权限错误,也会引入新的数据完整性风险。

容器如果把日志发往 stdout/stderr,应检查容器运行时的日志驱动与采集链路。宿主机轮转普通文件的配置不能直接套在标准输出上。

验收要覆盖下一次自动轮转

手动 reopen 后,先确认目标访问请求出现在当前日志,再检查旧文件是否停止增长、Nginx 是否释放旧句柄。刚好没有业务流量时,可以对已确认无副作用的源站页面发一条标记请求;如果启用了缓冲,应结合实际 flush 设置等待。

还需要记录下一次自动轮转的结果。检查新文件属主和权限、轮转脚本退出状态,以及监控或采集器是否切到新文件。若 Nginx 已写入新日志,而日志平台仍无数据,应继续排查采集器的文件发现、权限和读取偏移。

回滚应恢复原轮转规则和被修改的权限,保留现场日志。已删除且释放的日志无法靠回滚配置恢复;被压缩或归档的文件也不应贸然改名覆盖当前日志。

故障闭环的证据应包括当前文件中的新请求、已经释放的旧句柄,以及一次成功的自动轮转。只看到空文件重新生成,还不足以确认修复。

参考资料

资料核验日期:2026 年 11 月 5 日。示例需按实际安装方式、发行版与日志策略调整。

Web服务与建站

网站接口偶尔返回 405?请求方法、反向代理与预检请求排查

2026-11-4 13:39:41

知识库

多台服务器文件怎么同步?rsync + inotify 实时同步实战

2026-4-27 14:54:19