
网站访问正常,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 已写入新日志,而日志平台仍无数据,应继续排查采集器的文件发现、权限和读取偏移。
回滚应恢复原轮转规则和被修改的权限,保留现场日志。已删除且释放的日志无法靠回滚配置恢复;被压缩或归档的文件也不应贸然改名覆盖当前日志。
故障闭环的证据应包括当前文件中的新请求、已经释放的旧句柄,以及一次成功的自动轮转。只看到空文件重新生成,还不足以确认修复。
参考资料
- [1] Nginx 官方:Controlling nginx,Rotating Log-files,https://nginx.org/en/docs/control.html
- [2] Nginx 官方:Command-line parameters,https://nginx.org/en/docs/switches.html
- [3] Nginx 官方:ngx_http_log_module,https://nginx.org/en/docs/http/ngx_http_log_module.html
- [4] logrotate 官方手册源码:copytruncate、postrotate、sharedscripts 与调试模式,https://github.com/logrotate/logrotate/blob/main/logrotate.8.in
- [5] GNU Coreutils 官方:tail invocation,https://www.gnu.org/software/coreutils/manual/html_node/tail-invocation.html
资料核验日期:2026 年 11 月 5 日。示例需按实际安装方式、发行版与日志策略调整。




