Skip to main content

Command Palette

Search for a command to run...

一次Nginx 403 的问题排查

Updated
一次Nginx 403 的问题排查

前言&问题复现

参与了一个内部效率提升项目(边角料项目)后要发到内部的测试机器上。内部的测试机器上并没有配置集群,没有一个专门的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

https://linux.vbird.org/linux_basic_train/centos8/unit10.php

More from this blog

Go 隐式接口与模板方法

前言 今天在使用testify框架写单元测试的时候有这样一个需求: 对于一个方法来说,可能会有很长的上下文链路数据。 按照正常的单元测试流程,这个时候我们需要按照接口的逻辑来事先mock好原始未处理的数据,并且定义最终想要的数据结果。定义好不同的test case 尽可能的覆盖到每一个if else,才可以通过后续的ci 流程。 对于一些特殊的case,我们需要一些特殊的操作: 测试前置处理-> 运行测试代码 -> 测试后处理 需要在测试前后对数据进行预处理,如:事先存入一些数据,测试后再删除...

Nov 13, 2025
Go 隐式接口与模板方法

[Learn With Agent] JSX & React Components

前言 这是Learn With Agent 的第一篇博客,这个系列(不知道有没有后续了)是想通过agent辅助进行快速的学习(过概念)掌握一些相关的知识。之后再通过agent辅助开发,应该也能做出来个7788。对于AI还有很多思考,另起一个博客再说吧就。 因为网络和经费原因,这里使用的是CC+GLM 4.6 & Gemini 系列。🈚️广,文字为百分百人类手敲&传统CV大法。 JSX JSX是React生态中占据很重要的部分,JSX是一种语法拓展,它可以允许用户在js中写类似html的标签结构...

Oct 9, 2025
[Learn With Agent] JSX & React Components

提示词以及常见优化技巧

prompt 种类 system : 系统提示词用于统一设定当前会话下ai的行为,例如设定ai的行为、语气、风格或限制。用户不会显式观察到系统提示词,但是会影响到用户和ai的后续对话。 user : 这是用户实际向ai提出的问题,这是对话的起点。 assistant: 这是ai对于用户问题的回答,通常也会被添加到上下文中,供下一次对话进行参考,所以也属于prompt的范畴。 prompt 格式 标准格式: <Instruction> 问答格式: <Question>? 零样本提示 ...

Aug 14, 2025
提示词以及常见优化技巧

Http 长连接 & 短连接详解

网络连接基础 TCP/IP TCP/IP,是几乎所有互联网通信的基石。HTTP、WebSocket 和 常见的RPC框架尽管功能各异,但都运行在应用层,并从根本上依赖传输层的 TCP(传输控制协议)来实现可靠的、面向连接的数据传输 。 TCP 的核心职责是确保数据包从发送端到接收端可靠、按序且无损地传输 。这包括序列号、确认、流量控制和拥塞控制等机制。在网络层,IP(互联网协议)负责网络路由和寻址,使数据能够跨越不同网络到达其目的地 。现代操作系统普遍内置并管理 TCP/IP 协议栈,从而为应用...

Jun 13, 2025
E

Ekreke's Blog

11 posts