前言&问题复现
参与了一个内部效率提升项目(边角料项目)后要发到内部的测试机器上。内部的测试机器上并没有配置集群,没有一个专门的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 剩余部分(即匹配部分被替换掉) |
使用范围 | 可以在 server 或 location 块中使用。 |
只能在 location 块中使用。 |
应用场景 | 物理目录结构与 URI 结构一致时。 | 物理目录结构与 URI 结构不一致时(路径重映射)。 |
斜杠建议 | 保持 location 路径与文件系统结构一致即可。 |
建议 location 和 alias 路径都以 / 结尾,以避免错误。 |
来个🌰:
指令 | 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 就可以正常的访问静态资源了。
为什么会发生这个问题?
这个问题发生的核心原因是权限的错位。
- 用户身份冲突: 负责上传文件的应用进程是root用户,和负责提供访问服务的 Nginx 工作进程(www-data)不是同一个用户。
- 权限级别降级: Nginx 进程 (www-data) 无法以文件的"所有者"或"所属组"身份访问,只能被归为"其他用户 (Other)"类别。
- umask 限制: 文件在创建时,受应用程序当前运行环境的 umask 影响,默认权限被设置为 644 或更严格,屏蔽了"其他用户"所需的读 (r) 权限。
- 目录遍历受阻: 即使文件有读权限,从根目录到文件的路径链上,任意一级目录对 www-data 用户("其他用户")缺少执行 (x) 权限,也会导致 Nginx 无法定位和打开文件。
- 最重要的一点,我们绑定的静态资源路径是"/data" 下 而不是 "/var"下。/var 目录是 Linux 文件系统层级标准(FHS)规定的用于存放经常变化文件的地方。默认的权限就是755,包含了O用户的r和x权限。所以说我们正常在/var 目录下绑定资源,很少会遇见这种问题。
最佳实践
-
预创建目录并设置权限
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
-
使用标准路径
- 对于nginx引用的内容,尽量使用 var/www 或 /usr/share/nginx/html
- 如果必须使用自定义路径,确保设置了正确的权限。
-
设置合理的umask
bash# 在应用启动脚本中设置 umask 022 # 新文件权限 644,新目录权限 755
Reference
linux.vbird.org/linux_basic...
注: 部分示例由ai生成