CentOS 7 下 Nginx 访问自定义目录报 403 的完整排查:从端口权限到 SELinux

背景

在 CentOS 7 上用 Nginx 部署一个静态站点,root 指向非标准目录 /data/www,监听自定义端口 12000。过程中连续踩了两个坑:

  1. Nginx 启动失败:bind() to 0.0.0.0:12000 failed (13: Permission denied)
  2. 启动成功后访问页面:"/data/www/index.html" is forbidden (13: Permission denied)

两个错误的共同点都是 13: Permission denied ,但根因都是 SELinux,而不是普通的文件权限或端口占用。本文把整个排查过程整理出来,供遇到类似问题的同学参考。


环境

  • CentOS Linux 7 (Core)
  • Nginx 反向代理 + 静态站点
  • 站点根目录:/data/www
  • 监听端口:12000
  • 代理上游:127.0.0.1:180
  • SELinux:Enforcing

问题一:Nginx 启动失败,端口绑定被拒

现象

复制代码
systemctl status nginx.service

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: [emerg] bind() to 0.0.0.0:12000 failed (13: Permission denied)
nginx: configuration file /etc/nginx/nginx.conf test failed

原因分析

12000 虽然是高于 1024 的端口,普通用户理论上可以绑定,但在 SELinux Enforcing 模式下,SELinux 策略默认只允许 HTTP 服务绑定 http_port_t 类型里的端口。12000 不在允许列表中,所以 Nginx 的 worker 进程(非 root)绑定该端口时被 SELinux 拒绝。

解决方案

把 12000 加入 http_port_t 类型:

bash 复制代码
semanage port -a -t http_port_t -p tcp 12000

# 验证
semanage port -l | grep http_port_t

如果 semanage 不存在,先安装:

bash 复制代码
yum install -y policycoreutils-python

然后重启 Nginx:

bash 复制代码
systemctl restart nginx

备选方案

  • 临时验证:setenforce 0 后重启,能启动即确认是 SELinux 问题,但不推荐长期关闭。
  • 开启 httpd_can_network_connect:可以解决绑定问题,但这个布尔值本来是放行出站连接的,用它解决绑定问题会放宽不必要权限,有安全风险。

推荐方案一:只放行单个端口,最小化改动。


问题二:页面 403 Forbidden

现象

Nginx 启动正常,访问 http://10.2.2.240:12000/ 有请求日志,但返回 403:

复制代码
"/data/www/index.html" is forbidden (13: Permission denied),
client: 10.2.2.240, server: localhost, request: "GET / HTTP/1.1",
host: "10.2.2.240:12000"

同时另一条日志:

复制代码
connect() to 127.0.0.1:180 failed (13: Permission denied) while connecting to upstream

排查过程

1. 先排除认证问题

配置里有:

nginx 复制代码
satisfy any;
allow 192.168.34.0/24;
deny all;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;

satisfy any 表示 IP 限制 和 Basic 认证 满足任意一个即可通过。

用 curl 分别测试:

bash 复制代码
# 密码错误 → 401
curl -v -u admin:admin123 http://10.2.2.240:12000/
# HTTP/1.1 401 Unauthorized

# 密码正确 → 403
curl -v -u admin:admin@123 http://10.2.2.240:12000/
# HTTP/1.1 403 Forbidden

从 401 变成 403 说明认证已通过,403 是认证之后的文件访问被拒,与登录凭据无关。错误日志里也明确写了:

复制代码
user "admin": password mismatch   # 之前密码错
"/data/www/index.html" is forbidden (13: Permission denied)  # 认证通过后读文件被拒
2. 排除普通 Linux 权限
bash 复制代码
namei -l /data/www

输出:

复制代码
dr-xr-xr-x root   root  /
drwxr-xr-x root   root  data
drwxrwxrwx gtdata sftpg www
bash 复制代码
ls -l /data/www/index.html
# -rwxrwxrwx. 1 gtdata sftpg 194818 Sep  1 13:57 /data/www/index.html

路径每一级都有 x 权限,文件本身也是 777。普通 Linux 权限完全没问题,所以排除。

3. 锁定 SELinux

既然普通权限没问题,而报错又是 13: Permission denied,同时还有对 127.0.0.1:180 的出站连接也被拒,两个现象高度一致地指向 SELinux。

bash 复制代码
getenforce
# Enforcing

ls -Z /data/www/index.html
# 上下文不是 httpd_sys_content_t

/data/www 不在标准 Web 目录下,SELinux 没有给它 httpd_sys_content_t 上下文,Nginx 读文件就被拦。

解决方案

bash 复制代码
# 1. 添加永久上下文规则
semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"

# 2. 应用规则
restorecon -Rv /data/www

# 3. 验证
ls -Z /data/www/index.html

执行后再访问,返回 200,页面正常。问题解决。


完整修复清单

针对本次的两个问题,最终执行的命令:

bash 复制代码
# 端口:允许 Nginx 绑定 12000
semanage port -a -t http_port_t -p tcp 12000

# 文件:给 /data/www 打上 httpd_sys_content_t 上下文
semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"
restorecon -Rv /data/www

# 上游:若 Nginx 需要反代本机其他端口,放行出站连接
setsebool -P httpd_can_network_connect 1

# 重启
systemctl restart nginx

验证:

bash 复制代码
curl -v -u admin:admin@123 http://10.2.2.240:12000/
# HTTP/1.1 200 OK

经验总结

1. 13: Permission denied 在 CentOS 7 上要优先怀疑 SELinux

普通权限用 ls -l、namei -l 一看便知,如果这些都没问题,但日志仍报 Permission denied,基本就是 SELinux 。用 getenforce 和 ls -Z 确认。

2. 自定义端口要单独放行

SELinux 不会因为端口 > 1024 就自动放行,必须把端口加入 http_port_t,否则 Nginx 启动阶段就会失败。

3. 非标准目录要打上下文

/data/www 这类自定义路径不在 SELinux 预定义的 Web 目录里,需要 semanage fcontext + restorecon 显式指定 httpd_sys_content_t。

4. Nginx 反代本机端口要开布尔值

httpd_can_network_connect 控制 Nginx 能否主动发起网络连接。反代 127.0.0.1:180 这类上游时,如果没开,会看到:

复制代码
connect() to 127.0.0.1:180 failed (13: Permission denied)

5. 排查顺序建议

  1. tail -f /var/log/nginx/error.log --- 看具体报错
  2. namei -l <路径> --- 排除普通权限
  3. getenforce --- 确认 SELinux 状态
  4. ls -Z <路径> --- 看 SELinux 上下文
  5. semanage port -l | grep http_port_t --- 看端口是否放行

按这个顺序走,绝大多数 Nginx 的 Permission denied 都能快速定位。

6. 不要动辄关闭 SELinux

setenforce 0 只适合临时验证。生产环境应通过 semanage、restorecon、setsebool 精确放行,既解决问题又不牺牲安全性。


参考

  • man semanage-fcontext
  • man nginx
  • CentOS 7 SELinux 官方文档
  • Nginx 官方文档:satisfy、auth_basic、try_files
相关推荐
lpfasd1231 天前
WinSW在Win7上失败真相-实测与修复
windows·nginx
lpfasd1231 天前
nginx服务化五套方案原理解剖与选型
运维·nginx
ue星空1 天前
ALS V4 中文拆解手册
运维·nginx
吴声子夜歌2 天前
Nginx应用与运维——Nginx集群负载与配置管理(二)
运维·nginx
cpolar技术支持2 天前
外网测试机的异常传不回内网?自托管 Sentry,用 cpolar 打通错误上报链路
python·nginx·docker·cpolar·sentry
吴声子夜歌2 天前
Nginx应用与运维——Nginx在Kubernetes中的应用(一)
运维·nginx·kubernetes
螺蛳粉 螺蛳粉2 天前
第一篇:Keepalived 高可用实战:VIP 漂移与 Nginx 主备切换完整指南
运维·nginx·负载均衡·keepalived·高可用
吴声子夜歌2 天前
Nginx应用与运维——Nginx在Kubernetes中的应用(三)
运维·nginx·kubernetes
吴声子夜歌2 天前
Nginx应用与运维——Nginx在Kubernetes中的应用(二)
运维·nginx·kubernetes
Wang's Blog3 天前
Java 项目实战: 外卖平台优化-Nginx配置文件结构与块层级
java·开发语言·nginx