上一篇 下一篇 分享链接 返回 返回顶部

新加坡服务器上的Nginx报403,如何检查目录权限并按最小权限修复

发布人:Minchunlin 发布时间:6小时前 阅读量:13
新加坡服务器上的Nginx报403,如何检查目录权限并按最小权限修复

文件已经设为 644,Nginx 仍然返回 403,并不矛盾。文件可读不代表路径可达:只要某一级父目录不允许 Nginx 工作进程穿越,请求就可能被拒绝。不过,403 也可能来自缺少首页、访问规则或上游应用,不能看到状态码就直接修改权限。

排查新加坡服务器上的这类问题,应依次确认:请求命中的站点与错误日志 → Nginx worker 的实际身份 → 文件及每一级父目录权限 → ACL 与安全策略。只有确认是文件系统权限不足,才为实际服务身份补齐必要权限;不要使用 chmod -R 777,也不要把整个站点交给 Nginx 所有。

先区分:403 是否真的由目录权限引起

以下示例适用于 Linux 上直接部署的 Nginx,站点路径假设为 /srv/www/site/public。路径、域名和用户名均需替换为实际值。检查命令大多只读;修改权限的步骤需要 root 权限,并应先备份。容器部署需在对应容器或挂载命名空间中核验,不能直接套用宿主机用户名。

先查看生效配置:

sudo nginx -T

重点检查命中请求的 server_namelistenlocationrootalias,以及 indextry_fileserror_log。如果服务通过自定义配置文件启动,应使用相同的 -c-p 参数,避免检查到另一份配置。

nginx -T 可能包含内部地址或认证配置,不要未经脱敏公开完整输出。

针对同一个请求查看错误日志:

# 替换为实际 error_log 路径
sudo tail -n 100 /var/log/nginx/error.log
日志或现象更可能的原因下一步
open() "...index.html" failed (13: Permission denied)路径权限、文件权限、ACL 或安全策略拒绝访问检查 worker 身份与完整路径
directory index of "..." is forbidden请求落到目录,没有可用首页且未开启目录列表检查 index、首页文件及路径映射
access forbidden by ruleNginx 访问控制规则拒绝请求检查命中的 allowdeny 等配置
请求经 proxy_pass 转发,403 来自上游上游应用拒绝访问不要先修改本地静态目录权限

Permission denied 说明发生了访问拒绝,不等于一定是传统的 chmod 权限位错误。SELinux、AppArmor 也可能产生类似现象。

如果日志指向目录首页问题,可以请求一个已知存在的静态文件。静态文件正常而目录 URL 返回 403 时,应优先核对首页配置,不要通过开启 autoindex 掩盖问题,以免暴露目录内容。

为什么文件可读,Nginx 仍然访问不了

对普通静态资源请求,Nginx 通常需要:

  • 对路径中的每一级目录具有 x,即搜索、穿越权限。
  • 对最终文件具有 r,即读取权限。
  • 不需要对静态文件具有 x,也不需要对站点目录具有 w

目录的 r 允许列出目录项,x 允许通过已知名称访问目录内对象。普通静态文件读取通常不要求列出整个目录,因此只为访问已知路径补权限时,目录 x 比目录 rwx 更符合最小权限原则。

例如,访问:

/srv/www/site/public/index.html

即使 index.html644,如果 /srv/www/site700,所有者又不是 worker 用户,Nginx 仍然可能无法穿越这一级目录。

确认执行读取的是谁

不要用 master 进程的 root 身份判断站点权限。正常服务请求通常由 worker 处理:

ps -eo pid,ppid,user,group,args | grep '[n]ginx'

找到 nginx: worker process 对应的用户。发行版和安装方式不同,账号可能不同,应以实际输出为准。

假设检查结果是 www-data

id www-data

# 将 1234 替换为实际 worker PID
sudo grep -E '^(Uid|Gid|Groups):' /proc/1234/status

id 展示账号当前的用户组信息,/proc 展示运行中进程的实际身份和附加组。刚调整过组成员关系时,两者可能不一致。

后续 sudo -u www-data 测试会按账号当前身份启动新进程,不会自动复现旧 worker 的所有运行条件。若依赖新增用户组修复,应按服务管理方式使 worker 更新身份,再核验 /proc;重启可能影响连接,应安排维护窗口,而不是只凭 id 判断已经生效。

沿实际路径检查,而不是只看站点目录

先检查每一级目录及目标文件:

namei -l /srv/www/site/public/index.html

输出会逐层列出路径的权限、所有者和所属组。根据 worker 的身份逐级判断:

  • worker 是所有者时,先看所有者权限。
  • 不属于所有者时,再结合所属组、附加组及其他用户权限判断。
  • 存在扩展 ACL 时,还要检查命名用户、组及 ACL mask,不能只看 ls -l

权限类别并不是“这一组不允许,就自动尝试下一组”。例如用户已经匹配文件所有者,不会再借用其他用户权限获得访问能力。

检查 ACL 和实际读取能力:

sudo getfacl -p /srv /srv/www /srv/www/site \
  /srv/www/site/public /srv/www/site/public/index.html

sudo -u www-data -- test -x /srv/www/site
echo $?

sudo -u www-data -- test -r /srv/www/site/public/index.html
echo $?

sudo -u www-data -- head -c 1 /srv/www/site/public/index.html >/dev/null
echo $?

紧跟命令的退出码 0 表示成功,非零表示失败。head 会实际打开并读取文件,比只观察权限位更直接。

如果系统缺少 nameigetfaclsetfacl,先确认发行版,再使用对应包管理器安装所需工具,不要混用不同系统的安装命令。

还应检查软链接:

readlink -f /srv/www/site/public/index.html

如果发布目录指向另一个版本目录,必须检查目标路径及其父目录。链接所在目录权限正确,并不能保证链接目标可访问。若日志中的路径与预期不一致,先修正 rootalias 或内部跳转映射,不要给错误目录增加权限。

按最小权限修复:只授权读取所必需的路径

静态站点通常应由发布账号维护,Nginx 只负责读取。把文件所有者改成 worker 用户,可能让服务进程获得原本不需要的修改能力;将目录统一设成 755,则可能扩大其他本地账号的访问范围。

对于“仅缺少一个服务账号的读取权限”这一情况,可以使用 ACL 精确授权。前提是文件系统支持 POSIX ACL,且已确认实际 worker 用户。

下面假设:

  • worker 用户确实是 www-data
  • /srv/srv/www 已允许穿越。
  • /srv/www/sitepublic 缺少穿越权限。
  • index.html 缺少读取权限。

修改前备份并检查影响范围

在 root shell 中执行以下命令;操作仅涉及列出的路径,不递归修改整个站点:

sudo -i

backup=$(mktemp /root/nginx-perms.XXXXXX)
getfacl -p /srv /srv/www /srv/www/site \
  /srv/www/site/public \
  /srv/www/site/public/index.html > "$backup"

printf '权限备份:%s\n' "$backup"

确认备份成功且内容完整后,再预览变更:

setfacl --test -m u:www---x \
  /srv/www/site /srv/www/site/public

setfacl --test -m u:www-r-- \
  /srv/www/site/public/index.html

ACL 的 mask 会限制命名用户及组条目的有效权限。 setfacl 默认可能重新计算 mask,如果原有条目受 mask 限制,扩大 mask 可能同时恢复其他主体的权限。因此应将预览与 getfacl 输出对照;存在复杂 ACL 时,先确认不会扩大无关账号权限,再执行。

仅补齐已确认缺失的权限

setfacl -m u:www---x \
  /srv/www/site /srv/www/site/public

setfacl -m u:www-r-- \
  /srv/www/site/public/index.html

这组修改不授予写权限,也不改变文件所有者。静态页面引用的 CSS、JavaScript、图片若位于其他路径,还需要逐项确认相应目录可穿越、文件可读。

若整个发布树都要授权,应先明确其中是否混有密钥、备份或应用配置,再划定范围;不要直接对混合内容目录递归授权。新增文件是否继承权限,也取决于发布方式:临时目录构建后切换软链接、移动或替换文件,都可能让一次性修复失效。

权限变更通常无需 reload Nginx,后续打开文件时即可使用新权限。

需要撤销时恢复备份

在同一 root shell 中执行:

setfacl --restore="$backup"

若已退出该 shell,应把变量替换为之前记录的备份绝对路径。恢复仅针对备份列出的对象,不会撤销其他配置修改;文件若在此期间被发布流程替换,应先核对当前对象,避免覆盖后续的合法权限调整。

权限看似正确时,检查 ACL 与安全策略边界

如果 getfacl 中出现:

user:www-r--
mask::---

命名用户虽然写着可读,实际仍受 mask 限制。应结合输出中的 effective 权限判断,而不是只看单个条目。

如果以 worker 用户执行读取成功,但真实 HTTP 请求仍因访问拒绝失败,需要继续检查进程安全上下文。以 SELinux 系统为例:

getenforce
ls -Zd /srv/www/site /srv/www/site/public
ls -Z /srv/www/site/public/index.html
sudo ausearch -m AVC -ts recent

若启用了 AppArmor,可查看:

sudo aa-status
sudo journalctl -k --since "10 minutes ago" | grep -i apparmor

这些命令仅适用于已安装对应组件的系统。应将拒绝记录中的时间、进程和路径与请求对应起来。不要把关闭 SELinux、AppArmor 或放宽整个站点权限当作常规修复。 确认策略拒绝后,应备份现有规则,按实际目录用途调整持久标签或对应 profile,并记录恢复方法。

如果进程看到的路径与宿主机不同,还需核验服务沙箱、容器挂载或 chroot;此时宿主机上的读取测试并不能证明 worker 一定可读。

用同一个请求验证,避免“修好了却测错站点”

修复后先重复 worker 身份下的读取测试,再使用原域名、协议、端口和 URI 发起请求。以下适用于本机 Nginx 监听 HTTP 80 端口的情况:

curl -sS -D - -o /dev/null \
  --resolve example.com:80:127.0.0.1 \
  http://example.com/index.html

HTTPS 应使用实际域名和 443 端口,使 Host 与 TLS SNI 同时匹配;若 Nginx 未监听回环地址,则替换为实际监听地址。不要只请求 IP,否则可能命中默认站点。

验收时至少确认:

1. 已知静态文件返回预期响应,访问日志记录的是本次请求。

2. 同一时间的错误日志不再出现对应路径的 Permission denied

3. 目录 URL 能按预期找到首页,页面依赖的静态资源也可读取。

4. getfacl 显示 worker 只获得所需权限,没有意外增加写权限。

5. 再次发布后权限仍符合预期,不会因替换文件而复发。

如果静态文件已经可读,但目录 URL 仍返回 403,应回到首页和路径映射检查;如果日志不再报告文件访问失败,而是上游返回 403,就应停止扩大目录权限。新加坡服务器上的 Nginx 权限修复是否到位,最终要由实际 worker 身份、实际请求路径和对应日志共同证明,而不是由某个统一的 chmod 数值决定。

目录结构
全文