一次Nginx 403 的问题排查

659 阅读9分钟

前言&问题复现

参与了一个内部效率提升项目(边角料项目)后要发到内部的测试机器上。内部的测试机器上并没有配置集群,没有一个专门的ingress或者说是网关来处理请求分发。

并且这个测试机器属于多个部门,导致机器环境很复杂,一台物理机安装有多个nginx,有直接host安装的,也有在容器上运行的。

由于这是一个内部项目,没有必要专门部署一个minio,but 项目需要上传文件,所以就直接保存在server的目录下,简单配置了一下nginx的配置,配置如下:

    location /static/ {
        alias /data/www/nuwa/uploads/;
        # 禁用autoindex(避免目录被列出)
        autoindex off;
        # 设置缓存头(可选)
        expires 30d;
        # 确保允许访问
        satisfy any;
        allow all;
        access_log /var/log/nginx/access_xxx.log;
    }

配置完成并且重启nginx后,前端上传文件成功但是查询返回​​403 Forbidden 错误。

Nginx 返回 403 Forbidden 错误通常表示服务器(Nginx)拒绝了客户端访问所请求资源的请求。这通常是权限或配置方面的问题。

走个弯路

根据以往的经验,403 的问题通常是由于路径配置错误导致的,这里是使用的是alias,来快速过一下alias 和root 的区别。

区别:

特性root 指令alias 指令
工作原理追加 (Append):将 URI 完整追加到指定路径后。替换 (Substitution):用 alias 路径替换 location 中匹配到的 URI 部分。
最终路径root 路径 + 完整 URIalias 路径 + URI 剩余部分(即匹配部分被替换掉)
使用范围可以在 serverlocation 块中使用。只能在 location 块中使用。
应用场景物理目录结构与 URI 结构一致时。物理目录结构与 URI 结构不一致时(路径重映射)。
斜杠建议保持 location 路径与文件系统结构一致即可。建议 locationalias 路径都以 / 结尾,以避免错误。

来个🌰:

指令NGINX 配置用户请求最终查找的物理路径
rootlocation /static/ { root /var/www; }/static/js/app.js/var/www/static/js/app.js
aliaslocation /static/ { alias /data/files/; }/static/js/app.js/data/files/js/app.js

经检查,路径配置正确,上传文件可以正常上传,但是尝试读取的时候会报403,这就很奇怪。

权限&用户问题排查

在检查nginx日志的时候发现如下输出:

2025/07/10 17:50:53 [error] 51412#51412: *1 open() "/data/www/xxx/uploads/2025/07/10/39855bb0-bbbc-43bd-a053-860457276413.png" failed (13: Permission denied), client: 10.66.67.150, server: _,localhost, request: "GET /static/2025/07/10/39855bb0-bbbc-43bd-a053-860457276413.png HTTP/1.1", host: "10.4.20.4:28001"

这里的蛛丝马迹似乎在指明,这是一个由文件访问权限导致的异常。

当讨论到权限问题,用户以及角色问题就很明显是绕不开的话题了。

Nginx 在其运行中通常涉及到两个主要的用户角色,具体使用哪个非特权用户取决于操作系统发行版或安装方式了。

Nginx的用户种类

1. Nginx 进程的运行用户(非特权用户)

这是 Nginx 工作进程 (Worker Processes) 实际运行时所使用的用户,它是出于安全考虑而设置的。

这个用户拥有对网站文件进行读取(r)和对目录进行遍历(x)的权限。

根据不同的操作系统或安装方式,默认的非特权用户通常是以下之一:

用户名常见于描述
www-data基于 Debian 的系统(如 Ubuntu)这是 Ubuntu 等系统中最常见的 Web 服务器用户。
nginx基于 Red Hat 的系统(如 CentOS、RHEL、Fedora)和官方 Docker 镜像许多较新的发行版和官方软件包倾向于使用专用的 nginx 用户。
nobody源码编译安装或某些极简的系统,以及 Nginx 官方文档中的默认值如果配置文件中没有显式设置 user 指令,Nginx 可能会回退到这个用户。

这个用户是由 Nginx 配置文件 nginx.conf 中 user 指令 所决定的,通常位于文件顶部:

Nginx

# 示例:Debian/Ubuntu 上的默认配置
user www-data;
worker_processes auto;

如果看到 user 指令设置了哪个用户,那么 Nginx 的工作进程就会以这个用户的身份运行。

2. Master 进程用户(特权用户)

Nginx 启动时,会有一个主进程 (Master Process) 负责读取配置文件、管理工作进程,以及绑定到端口(尤其是低于 1024 的端口,如 HTTP 的 80 端口和 HTTPS 的 443 端口)。

  • 这个主进程通常以 root 用户的身份运行。

这是必要的,因为只有 root 用户才有权限绑定到 1024 以下的特权端口。一旦绑定成功,主进程就会创建非特权用户的工作进程来处理实际的客户端请求,以最大限度地保障安全性。

进程类型运行用户目的
Master Process(主进程)root启动、绑定特权端口(如 80/443)、管理工作进程。
Worker Process(工作进程)www-data、nginx 或 nobody处理客户端请求、访问网站文件。出于安全考虑,使用非特权用户。

深入权限排查

来检查nginx worker 进程的运行用户

commend: ps aux | grep nginx

可以看到worker process的角色是www-data,通过日志分析输出,基本可以确定问题出在文件权限上。

文件创建和权限继承

在案例中,文件是通过应用上传创建的:

# 创建的目录结构
/data/www/xxx/uploads/2025/07/10/xxx.png

查看文件权限:

-rw-r--r-- 1 root root 2 Jul 10 18:04 test.txt

问题就在这里:

  • 文件属主是 root
  • nginx worker 进程用户是 www-data
  • www-data 属于 other 用户组
  • 虽然 other 有读权限(r--),但是nginx访问文件,是需要x权限

Linux目录权限的特殊性

例如

# 创建测试目录
mkdir test_dir
chmod 644 test_dir  # drw-r--r-- 有读权限但无执行权限

# 尝试访问
cd test_dir          # 失败:Permission denied
ls test_dir          # 失败:Permission denied
cat test_dir/file    # 即使知道完整路径也失败

原因:目录的 x 权限控制用户能否:

  • 进入目录(cd)
  • 访问目录内的文件和子目录
  • 查看文件的元数据

没有 x 权限,我们即使有 r 权限也无法访问。

权限链

因此我们访问 /data/www/xxx/uploads/2025/07/10/xxx.png 需要:

/data     (x权限)
└── www   (x权限)
    └── xxx (x权限)
        └── uploads (x权限)
            └── 2025 (x权限)
                └── 07 (x权限)
                    └── 10 (x权限)
                        └── xxx.png (r权限)

任何一个环节缺少 x 权限,就不能正确的访问这个文件。

解决方案

使用ACL实现权限继承

ACL 的主要作用是允许你对文件或目录设置额外的、非标准的权限规则。我们默认的权限规则是通过UGO(user group other)和umask来进行控制的。

umask

在 Linux 中,umask 影响新创建文件权限的机制是固定的,并且遵循 文件基础权限 (666) 减去 umask 值的原则。umask 的值是一个八进制数字,用于屏蔽掉基础权限中的对应位。

Tips: 出于安全考虑,文件系统在创建文件时,默认不会赋予执行 (x) 权限,因此最大权限是 666,而不是 777。

规则:新文件/目录权限=文件/目录基础权限−umask

例如:

ekreke@ekrekes-MacBook-Air study % mkdir tmp
ekreke@ekrekes-MacBook-Air study % ls -alth
total 0
drwxr-xr-x@  6 ekreke  staff   192B Oct 18 17:43 .
drwxr-xr-x@  2 ekreke  staff    64B Oct 18 17:43 tmp
...
drwxr-xr-x@  7 ekreke  staff   224B Jun 13 13:19 ..
ekreke@ekrekes-MacBook-Air study % umask
022

但是这里会有一个问题,权限无法继承,umask 是一个全局或进程级的减法器,它与父目录当前设置的权限是无关的。无论父目录是 777 还是 700,新文件的权限都只取决于当前的 umask。

为了解决这个问题,我们可以使用ACL(Access Control Lists)用来在我们在父目录下创建新文件的时候,自动的分配文件访问所需要的权限,在此之前,我们需要设置一下目录的ugo权限。

# 递归修改所有权:将目录和所有内容的所有权交给Nginx运行用户
sudo chown -R www-data:www-data /data/www/nuwa/uploads

# 确保目录和父目录有执行权限
sudo chmod 775 /data/www/nuwa/uploads

# 确保所有父目录对Nginx work process 有执行权限
sudo chmod +x /data /data/www /data/www/nuwa

ACL设置:

# 1. 安装ACL工具(如果未安装)
sudo apt install acl

# 2. 设置默认ACL,让新创建的文件自动继承权限
sudo setfacl -R -d -m u::rwx,g::rx,o::rx /data/www/nuwa/uploads

# 3. 验证ACL设置
getfacl /data/www/nuwa/uploads

输出应该包含:

default:user::rwx
default:group::r-x
default:other::r-x

这样,给other分配了r,x作为 nginx的work process 就可以正常的访问静态资源了。

为什么会发生这个问题?

这个问题发生的核心原因是权限的错位。

  1. 用户身份冲突: 负责上传文件的应用进程是root用户,和负责提供访问服务的 Nginx 工作进程(www-data)不是同一个用户。
  2. 权限级别降级: Nginx 进程 (www-data) 无法以文件的“所有者”或“所属组”身份访问,只能被归为“其他用户 (Other)”类别。
  3. umask 限制: 文件在创建时,受应用程序当前运行环境的 umask 影响,默认权限被设置为 644 或更严格,屏蔽了“其他用户”所需的读 (r) 权限。
  4. 目录遍历受阻: 即使文件有读权限,从根目录到文件的路径链上,任意一级目录对 www-data 用户(“其他用户”)缺少执行 (x) 权限,也会导致 Nginx 无法定位和打开文件。
  5. 最重要的一点,我们绑定的静态资源路径是“/data“ 下 而不是 “/var”下。/var 目录是 Linux 文件系统层级标准(FHS)规定的用于存放经常变化文件的地方。默认的权限就是755,包含了O用户的r和x权限。所以说我们正常在/var 目录下绑定资源,很少会遇见这种问题。

最佳实践

  1. 预创建目录并设置权限

    # 部署时就设置好,var下是会有nginx所需的默认权限
    sudo mkdir -p /var/www/uploads
    sudo chown -R www-data:www-data /var/www/uploads
    sudo chmod 755 /var/www/uploads
    
  2. 使用标准路径

    1. 对于nginx引用的内容,尽量使用 var/www 或 /usr/share/nginx/html
    2. 如果必须使用自定义路径,确保设置了正确的权限。
  3. 设置合理的umask

    # 在应用启动脚本中设置
    umask 022  # 新文件权限 644,新目录权限 755
    

Reference

linux.vbird.org/linux_basic…


注: 部分示例由ai生成