
前言先澄清一个容易误导人的前提这条报错不会出现在 access_log 文件里而是出现在 error_log 里而且它通常不会让 nginx 启动失败。真实症状是这样的——网站访问一切正常页面 200systemctl status nginx也是绿的但access.log永远停在 0 字节或者你在error.log里翻到大量这样的行2026/09/29 10:12:33 [crit] 21984#0: *56 open() /data/wwwlogs/example.access.log failed (13: Permission denied) while logging request, client: 198.51.100.7, server: example.com, request: GET / HTTP/1.1[crit]级别、带while logging request后缀是这类问题的典型签名。因为 nginx 打不开日志文件时只记一条错误就继续服务所以很多人是在出事之后才发现访问日志已经断了好几天——排查线上问题要用日志结果发现日志本身就是空的这是最要命的地方。为什么会权限不足因为 nginx 是master/worker 模型master 进程以 root 启动这样才能监听 80/443、读取配置worker 进程则通过setuid降权到普通用户RHEL 系是nginxDebian/Ubuntu 是www-data再处理请求。而error_log通常由 master 打开、access_log由 worker 打开于是就会出现错误日志写得进去、访问日志写不进去这种看着自相矛盾的现场。再加上nginx -t是你在 root 下跑的它只检查语法不会替你验证 worker 的权限所以它通过不代表没问题。本文按「先确认是谁在写 → 逐层排查权限 → 落地修复」的顺序走一遍示例基于 RHEL 9 / Debian 12 / Ubuntu 22.04 上的 nginx 1.24nginx 从官方仓库或发行版仓库安装路径与包名在不同发行版有差异的地方都会标出来。一、先搞清楚是谁在写这个文件第一步不是看权限是确认 worker 跑在哪个用户下、以及这个access_log到底是哪一行配置指定的。# 1. 看 worker 进程的实际用户这是唯一权威答案 ps -o pid,user,group,args -C nginx # 2. 看 nginx 二进制里编译进去的默认用户仅作参考 nginx -V 21 | tr \n | grep -E ^--(user|group|prefix) # 3. 找出是谁设置了这个 access_log别改错文件 grep -rn access_log /etc/nginx/ps的输出会明确告诉你 worker 的用户名例如 RHEL 上是nginx、Debian 上是www-data。后面所有权限判断都以这个用户名为准不要凭发行版印象猜。两点必须记住主配置里的user指令只能写在 main 上下文只在 master 以 root 启动时生效。如果 nginx 由 systemd 以非 root 用户启动或者用的是nginx用户直接启动user指令会被忽略并给出警告。access_log用相对路径时是相对编译时的--prefix解析的不是相对/etc/nginx。这是另一类文件写不进去的常见来源报错通常是(2: No such file or directory)而不是(13: Permission denied)从 errno 就能区分开。顺带把 errno 对照表记住它能直接帮你缩小范围errno报错文本通常意味着13Permission denied传统权限位DAC、SELinux、AppArmor 三者之一拦住了2No such file or directory目录不存在或相对路径解析到了别处30Read-only file system挂载点是只读的与权限无关28No space left on device磁盘满或 inode 用尽容易和权限问题混为一谈二、逐层排查从内核到应用有四道关卡Linux 上「能不能写一个文件」要依次过四道关卡任何一道不过都表现为权限不足必须按顺序查跳步就会反复。2.1 第一道路径上每一级目录的权限位最常见的误判是只看了文件本身的权限忽略了目录的x执行/搜索位。目录没有x位即使里面的文件是666照样打不开。# namei -l 会把路径上每一级目录的权限都列出来一眼看出是哪一级断了 namei -l /data/wwwlogs/example.access.log输出示例f: /data/wwwlogs/example.access.log drwxr-xr-x root root / drwxr-xr-x root root data drwx------ root root wwwlogs # 问题在这里worker 用户对该目录没有 x 位 -rw-r----- root root example.access.log然后用 worker 用户亲自试一次这比看权限位可靠得多sudo -u nginx test -w /data/wwwlogs/example.access.log echo 可写 || echo 不可写 sudo -u nginx touch /data/wwwlogs/probe.log # RHEL 系也可以写runuser -u nginx -- touch /data/wwwlogs/probe.log2.2 第二道SELinuxRHEL / CentOS / Rocky / AlmaLinuxRHEL 系默认开启 SELinux传统权限位全部正确也可能被拒而且拒得没有任何传统手段能看出来。getenforce # Enforcing / Permissive / Disabled ls -Zd /var/log/nginx # 看目录的 SELinux 上下文 ls -Zd /data/wwwlogs # 对比自定义目录的上下文通常不对 ausearch -m avc -ts recent 2/dev/null | tail -20被 SELinux 拦住的日志长这样denied那条typeAVC msgaudit(1759123456.789:4321): avc: denied { write } for pid21984 commnginx nameexample.access.log devsda1 ino1234567 scontextsystem_u:system_r:httpd_t:s0 tcontextunconfined_u:object_r:default_t:s0 tclassfiletcontext的default_t就是没被任何策略认领的默认类型。nginx 的日志目录在 RHEL 策略里的类型是httpd_log_t。2.3 第三道AppArmorUbuntu / Debian / SUSEUbuntu 上 nginx 默认带一个 AppArmor 配置文件常见路径/etc/apparmor.d/usr.sbin.nginx以本机该目录下的实际文件名为准里面按路径白名单放行只允许写/var/log/nginx/**。把日志换到/data/wwwlogs就出了白名单。aa-status | grep -i nginx grep -n log /etc/apparmor.d/usr.sbin.nginx journalctl -k --since -1h | grep -i -E apparmor|DENIED被拒时内核日志里会出现类似apparmorDENIED operationopen profilenginx name/data/wwwlogs/...的记录。2.4 第四道挂载选项与 systemd 的沙箱findmnt -T /data/wwwlogs # 看 ro/rw、是否 tmpfs systemctl show nginx -p PrivateTmp -p ProtectSystem -p ReadWritePaths如果服务的 unit 里带了PrivateTmpyesnginx 看到的/tmp是该服务专属的私有目录写/tmp/xxx.log会落到/tmp/systemd-private-xxx-nginx.service-xxx/tmp/下面你在外面怎么找都找不到而ProtectSystemstrict之类的加固项会让除ReadWritePaths之外的路径一律只读。关卡检查命令典型修法DAC 权限位namei -l 路径chown/chmod/setfaclSELinuxls -Zd、ausearch -m avcsemanage fcontextrestoreconAppArmoraa-status、journalctl -k改配置文件后apparmor_parser -r挂载 / 沙箱findmnt -T、systemctl show改挂载选项或 unit 里的ReadWritePaths三、修复推荐做法与完整流程3.1 首选让日志目录归属 worker 用户这是最干净的做法和发行版自带日志目录的权限模型保持一致RHEL 系的/var/log/nginx由 nginx 包创建属主以本机ls -ld实测为准通常是nginx:nginxDebian/Ubuntu 的/var/log/nginx通常是www-data:adm目录0750、文件0640组adm让运维组能读日志。# RHEL 9日志目录让 nginx 用户可写 sudo chown -R nginx:nginx /data/wwwlogs sudo chmod 0750 /data/wwwlogs sudo chmod 0640 /data/wwwlogs/*.log # Debian 12 / Ubuntu 22.04worker 用户是 www-data sudo chown -R www-data:adm /data/wwwlogs sudo chmod 0750 /data/wwwlogs sudo chmod 0640 /data/wwwlogs/*.log改完让 worker 重新打开日志文件无需重启sudo nginx -s reopen # 等价于向 master 进程发送 USR1 信号reopen的语义是重新打开日志文件用于日志切割后让 nginx 丢掉旧的文件描述符。但要注意如果瓶颈是目录权限reopen之后 worker 依然打不开能不能成功要看 error_log 里有没有新的[crit]行。3.2 备选用 ACL 精确授权不想改属主时可以用 POSIX ACL 只给 worker 用户加权限# 已有文件 sudo setfacl -R -m u:nginx:rwX /data/wwwlogs # 关键默认 ACL让目录里将来新建的文件自动继承否则下次切分日志又会失败 sudo setfacl -R -d -m u:nginx:rwX /data/wwwlogs # 验证 getfacl /data/wwwlogs | grep -E nginx|default3.3 SELinux 场景的完整修复# 1. 先用 permissive 做一次性验证确认到底是不是 SELinux 拦的 sudo setenforce 0 sudo -u nginx touch /data/wwwlogs/probe.log echo SELinux 是元凶 ; rm -f /data/wwwlogs/probe.log sudo setenforce 1 # 立刻恢复不要留到忘记 # 2. 正式修给目录打正确的上下文标签再让策略文件生效 sudo semanage fcontext -a -t httpd_log_t /data/wwwlogs(/.*)? sudo restorecon -Rv /data/wwwlogs # 3. 复查 ls -Zd /data/wwwlogs如果第 1 步验证后业务仍然不通那瓶颈就不在 SELinux回到 2.1 节继续查 DAC 权限。3.4 AppArmor 场景的完整修复sudo cp /etc/apparmor.d/usr.sbin.nginx /etc/apparmor.d/usr.sbin.nginx.bak # 在 profile 的路径规则区里补一行注意行尾的逗号不能少 # /data/wwwlogs/** rw, sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx sudo aa-status | grep -i nginx3.5 别忘 logrotate这是修好了第二天又坏的根源手工chown修好之后第二天日志又断了八成是 logrotate 干的。轮转时新日志文件是由 logrotate 以 root 身份按配置创建的属主默认是root:root而它不会自动继承你手工设的属主除非你在配置里显式声明。# /etc/logrotate.d/nginx /data/wwwlogs/*.log { daily rotate 14 missingok notifempty compress delaycompress # 关键明确新文件的属主/属组/权限RHEL 写 nginx nginxDebian 写 www-data adm create 0640 nginx nginx # 或者用 su 指令指定轮转时的身份两种写法选一种以 logrotate 版本支持为准 # su nginx nginx sharedscripts postrotate [ -f /run/nginx.pid ] kill -USR1 $(cat /run/nginx.pid) endscript }改完立刻手动验证一次不要等到第二天才发现sudo logrotate -d /etc/logrotate.d/nginx # -d 是 dry-run只打印不执行 sudo logrotate -f /etc/logrotate.d/nginx # -f 强制执行一次 ls -l /data/wwwlogs/ # 确认新文件的属主和权限符合预期 tail -5 /var/log/nginx/error.log # 确认没有新的 [crit] 行常见坑点坑 1只改文件权限不管目录❌chmod 666 /data/wwwlogs/access.log结果还是Permission denied✅ 目录缺x搜索位时里面的文件根本打不开。用namei -l从根目录逐级看重点看wwwlogs那一级坑 2图省事上 777❌chmod -R 777 /data/wwwlogs既没解决 SELinux/AppArmor 的问题还给同机其他用户开了写日志的权限能伪造日志、能打满磁盘✅ 用chownchmod 0640/0750或者setfacl -m u:nginx:rwX坑 3以为nginx -t通过就没事❌nginx -t是 root 跑的只校验语法worker 的权限它一律不检查✅ 用sudo -u nginx test -w 路径或runuser -u nginx -- ...以 worker 用户身份实测坑 4logrotate 建出来的新文件回到 root 属主❌ 日志切分前好好的切完之后 access.log 再也不增长error.log 里重新出现[crit]✅ 在 logrotate 配置里写create 0640 nginx nginxDebian 写www-data adm或用su指令指定身份坑 5手工mv日志但没通知 nginx❌ 直接mv access.log access.log.1nginx 仍然持有旧的文件描述符往里写df显示磁盘不释放、新文件一直是空的✅ 用logrotate并在postrotate里发 USR1kill -USR1或nginx -s reopen如果是手工操作mv之后同样要发一次 USR1坑 6把日志写到/root、家目录或/tmp❌access_log /root/logs/access.log;——/root通常0700worker 连目录都进不去写/tmp还会撞上PrivateTmpyes✅ 统一放在/var/log/nginx或专门的日志挂载点下把权限模型一次配好坑 7临时setenforce 0之后忘了改回来❌ 排查时关了 SELinux问题好了就这么上线等于把整台机器的 SELinux 保护一起关了✅ 只把 permissive 当作一次性的判定手段确认是 SELinux 后立刻恢复 Enforcing再用semanage fcontextrestorecon正式修坑 8忘了open_log_file_cache❌ 以为改了权限 nginx 会立刻感知实际上 worker 会缓存已打开的日志文件描述符默认配置下不会每秒重开✅ 改完配置或用nginx -s reopen触发重开文件数量多的场景可以显式配置open_log_file_cache max1000 inactive20s valid1m min_uses2;该指令在http/server/location上下文可用默认是off总结排查步骤命令结论确认 worker 用户ps -o pid,user,args -C nginx后续权限判断的唯一依据确认是哪行配置grep -rn access_log /etc/nginx/避免改错文件逐级看权限位namei -l 日志路径目录缺x位是高频原因以 worker 身份实测sudo -u nginx test -w 路径比看权限位可靠查 SELinuxls -Zd、ausearch -m avc -ts recentRHEL 系必查查 AppArmoraa-status、journalctl -kUbuntu/Debian 必查查挂载与沙箱findmnt -T、systemctl show nginx只读挂载、PrivateTmp修复并验证chown/setfacl/restoreconnginx -s reopen再看 error_log 有没有新[crit]一句话记住这个问题的骨架error_log 由 masterroot打开、access_log 由 worker降权用户打开所以错误日志正常、访问日志写不了不是玄学。排查时永远用namei -l看整条路径、用sudo -u以真实身份实测、并额外检查 SELinux/AppArmor 这两道传统权限之外的关卡修完一定要把 logrotate 的create一起改掉否则明天还会再坏一次。