DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Nginx 静态文件报 403:父目录缺少搜索权限

Nginx 读取静态文件时出现 403 和 Permission denied,沿完整路径核对工作进程身份与目录搜索权限;对已确认的目录定向授权,并保留 ACL 备份与回退。

CSS、图片或 JavaScript 文件明明存在,访问时却返回 403 Forbidden,Nginx 错误日志里有类似内容:

open() "/home/deploy/site/static/app.css" failed (13: Permission denied)

文件本身可读,甚至把 static 目录放宽权限后仍然失败时,应检查完整路径上的父目录。Linux 查找文件需要逐层通过目录;只要 Nginx 工作进程对其中一层没有搜索权限,就到不了最后的文件。修改最里面的文件权限不会补上外层目录的缺口。

本文处理 Linux 上 Nginx 直接读取静态文件时的目录访问问题。以下路径和 www-data 都是示例,需要换成错误日志里的实际路径和运行用户。容器部署还要在容器的挂载与用户映射范围内判断,不能拿宿主机用户名直接代替容器里的身份。

用日志路径缩小范围

先核对同一次请求对应的错误。如果日志是 directory index ... is forbidden,那可能是在请求一个目录而没有可用首页,不能直接按文件权限问题处理。如果是 open() 或 stat() 对明确路径返回 13: Permission denied,才继续检查这条路径的访问条件。

日志中的路径也必须是你打算公开的文件。root、alias 或符号链接指向了错误目录时,应先修正路径,不应为了消除报错向错误位置开放权限。

查看 Nginx 工作进程的实际身份:

ps -eo user,group,pid,ppid,args

在输出中找到 nginx: worker process。主进程可能由 root 运行,这不代表处理请求的工作进程能以 root 身份读取文件。Nginx 的 user 指令用于设置工作进程身份,属于主配置上下文;不要把它加进站点的 server 段,也不要为了读静态文件直接把工作进程改成部署账号或 root。

逐层检查目录,而不是只看最后一个文件

namei 来自 util-linux,可以把路径每一层的权限和所有者展开:

namei -l /home/deploy/site/static/app.css

例如,若 /home/deploy 是 drwx------,所有者为 deploy,而工作进程用户为 www-data,没有额外 ACL 授权时,工作进程会在这一层被挡住。即使后面的 site、static 都是 755,app.css 是 644,也无法绕过它。

目录上的 x 表示搜索或穿过目录的权限;目录上的 r 用于读取目录项。对已知文件路径的访问需要经过每层目录的 x,最终读取普通文件还需要文件的 r。给普通 CSS 文件加可执行权限不能解决这个问题。

如果路径包含符号链接,也要沿它实际指向的目录检查。namei -l 会显示链接及其目标,不要只检查链接所在的表面路径。

再从工作进程用户的身份尝试读取一个已确认无敏感内容、体积较小的静态文件:

sudo -u www-data -- cat /home/deploy/site/static/app.css > /dev/null

成功表示这个用户在当前命令执行环境中能打开并读取文件;失败时继续定位父目录或文件本身。这个检查不会输出文件内容,但也不能完整模拟 Nginx 的 systemd 沙箱、容器命名空间或安全模块约束。

只给缺权限的目录增加必要访问

对于长期部署,把公开静态产物放在专用站点目录,并给 Web 工作进程配置专用的只读访问权限,通常比从私人家目录提供文件更容易维护。不要连同源码、密钥和备份一起作为静态根目录。

若必须保留当前路径,可以对已经确认缺少搜索权限的那一层定向授权。下面只适用于这个具体条件:/home/deploy 是普通目录、原模式为 700、所有者不是 Nginx 用户、没有扩展 ACL;其余父目录和目标文件已经满足读取条件。文件系统还必须支持 POSIX ACL,系统需有 getfacl 与 setfacl。

先在仅管理员可访问的工作目录保存这一个目录的权限记录,文件已存在时停止,避免覆盖上一次备份:

(
  umask 077
  set -C
  sudo getfacl -p /home/deploy > ./nginx-home-acl.before
)

确认命令成功、备份非空,并阅读其中的权限条目。该示例预期只有 user::rwx、group::---、other::--- 三个基础访问条目;若已有命名用户、命名组、mask 或默认 ACL,应先审查既有策略,不能照搬下一步。

先预览,再应用对指定用户的目录搜索权限:

sudo setfacl --test -m u:www-data:--x /home/deploy
sudo setfacl -m u:www-data:--x /home/deploy
sudo getfacl -p /home/deploy

--test 只显示结果,不修改权限。确认预览只增加预期的用户条目及相应掩码后,再执行修改。这里没有使用递归选项,也没有授予写权限或目录列表读取权限。

需要注意,目录的搜索权限并不是“只允许访问 app.css”。只要知道名字,工作进程也可能继续访问该家目录里其他权限允许的路径。应检查其中是否有原本依靠外层 700 隐藏的可读文件;有这类内容时,优先调整静态产物的部署位置,而不是开放家目录。

已有扩展 ACL 时尤其不能忽略 mask。它限制命名用户及组条目的有效权限,setfacl 默认可能重新计算它;放宽掩码可能同时放宽其他条目。仅凭新增的 user:www-data:--x 不能判断最终权限,需要同时检查 #effective 注释和其他条目。

权限检查通过但 Nginx 仍拒绝时

重新执行同用户读取检查,再访问原来的静态文件 URL,并查看这次请求的错误日志。普通文件权限或 ACL 改动通常不需要重启 Nginx;不要把重启数据库、应用和代理一起当成验证步骤。

如果命令行读取成功,实际请求仍然拒绝,先检查两者是否处在同一环境。systemd 的 ProtectHome=yes 会使服务看不到或无法访问 /home 等目录;read-only 和 tmpfs 的含义又不同,不能把它们都当成相同的权限位问题。容器中的挂载路径和用户编号也可能与宿主机检查不同。

这时继续增加 Unix 权限通常无效。应结合服务配置及系统拒绝日志,核对沙箱、SELinux 或 AppArmor 等额外访问限制。不要用关闭安全模块或递归 chmod 777 来跳过原因判断;如果采用专用站点目录,也应让它符合当前服务的安全策略。

回退定向授权

若这次授权不符合预期,且备份以后没有其他管理员修改该目录的权限或归属,可先预览恢复结果:

sudo setfacl --test --restore=./nginx-home-acl.before
sudo setfacl --restore=./nginx-home-acl.before
sudo getfacl -p /home/deploy

--restore 会按备份恢复权限,并可能根据记录恢复所有者、组及特殊模式位,因此必须先核对备份属于哪一个目录。存在后续合法改动时,应合并撤销本次授权,不能直接覆盖回旧状态。

恢复原先的目录隔离后,静态请求可能再次返回 403;这是撤销访问许可的结果。若之前已经执行过递归放宽权限,本次单目录备份无法恢复那些更早的修改,应从此前的权限记录或部署规则恢复,不能猜一组统一的 755、644 覆盖整个项目。