一次Nginx 403 的问题排查

前言&问题复现

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

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

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

ini 复制代码
    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 路径 + 完整 URI alias 路径 + URI 剩余部分(即匹配部分被替换掉)
使用范围 可以在 serverlocation 块中使用。 只能在 location 块中使用。
应用场景 物理目录结构与 URI 结构一致时。 物理目录结构与 URI 结构不一致时(路径重映射)。
斜杠建议 保持 location 路径与文件系统结构一致即可。 建议 locationalias 路径都以 / 结尾,以避免错误。

来个🌰:

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

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

权限&用户问题排查

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

vbscript 复制代码
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

ini 复制代码
# 示例: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 进程的运行用户

makefile 复制代码
commend: ps aux | grep nginx

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

文件创建和权限继承

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

bash 复制代码
# 创建的目录结构
/data/www/xxx/uploads/2025/07/10/xxx.png

查看文件权限:

css 复制代码
-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目录权限的特殊性

例如

bash 复制代码
# 创建测试目录
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 需要:

scss 复制代码
/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

例如:

perl 复制代码
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权限。

bash 复制代码
# 递归修改所有权:将目录和所有内容的所有权交给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设置:

php 复制代码
# 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

输出应该包含:

arduino 复制代码
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. 预创建目录并设置权限

    bash 复制代码
    # 部署时就设置好,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

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

Reference

linux.vbird.org/linux_basic...


注: 部分示例由ai生成

相关推荐
小蜗牛编程实录3 小时前
深入理解网络 IO:从基础模型到多路复用技术
后端
绝无仅有3 小时前
面试真实经历某商银行大厂数据库MYSQL问题和答案总结(二)
后端·面试·github
绝无仅有3 小时前
通过编写修复脚本修复 Docker 启动失败(二)
后端·面试·github
老K的Java兵器库3 小时前
并发集合踩坑现场:ConcurrentHashMap size() 阻塞、HashSet 并发 add 丢数据、Queue 伪共享
java·后端·spring
冷冷的菜哥4 小时前
go邮件发送——附件与图片显示
开发语言·后端·golang·邮件发送·smtp发送邮件
向葭奔赴♡4 小时前
Spring Boot 分模块:从数据库到前端接口
数据库·spring boot·后端
计算机毕业设计木哥4 小时前
计算机毕业设计选题推荐:基于SpringBoot和Vue的爱心公益网站
java·开发语言·vue.js·spring boot·后端·课程设计
IT_陈寒4 小时前
Redis 性能翻倍的 5 个隐藏技巧,99% 的开发者都不知道第3点!
前端·人工智能·后端
JaguarJack4 小时前
PHP 桌面端框架NativePHP for Desktop v2 发布!
后端·php·laravel