# 一次Nginx 403 的问题排查

# 前言&问题复现

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

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

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

```plaintext
    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日志的时候发现如下输出：

```plaintext
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

```plaintext
# 示例：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 进程的运行用户

```plaintext
commend: ps aux | grep nginx
```

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

### 文件创建和权限继承

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

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

查看文件权限：

```bash
-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 需要：

```plaintext
/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

例如：

```plaintext
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权限。

```plaintext
# 递归修改所有权：将目录和所有内容的所有权交给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设置:

```plaintext
# 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
```

输出应该包含：

```plaintext
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. **预创建目录并设置权限**
    
    ```plaintext
    # 部署时就设置好，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**
    
    ```plaintext
    # 在应用启动脚本中设置
    umask 022  # 新文件权限 644，新目录权限 755
    ```
    

# Reference

[https://linux.vbird.org/linux\_basic\_train/centos8/unit10.php](https://linux.vbird.org/linux_basic_train/centos8/unit10.php)
