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
相关推荐
正经教主16 分钟前
【FDE系列】阶段2:Day 45(up详细版):综合实战 — Docker + Nginx + Git 完整部署与本周收官
人工智能·git·nginx·docker·fde
甜到心里的蛋糕31 分钟前
AI 生成 MV 音频异响排查:频谱定位、片段替换与 Nginx 缓存
人工智能·python·nginx·ffmpeg·音视频·音频处理
行思理14 小时前
Nginx 跨站请求(CORS)配置完全指南:从原理到生产实践
java·前端·nginx
李迟20 小时前
Nginx容器绑定81端口报拒绝访问错误的解决
运维·nginx
杨云龙UP20 小时前
生产实战:Keepalived + Nginx + MySQL 双向复制高可用架构部署与原理详解
运维·数据库·mysql·nginx·架构·keepalived·高可用架构
国际云,接待1 天前
云服务器 WebSocket 空闲后频繁断开:查 Nginx 超时、心跳与负载均衡,再评估扩容
服务器·websocket·nginx
Linux界最忧郁之人1 天前
OpenStack Victoria双节点实战|Packstack一键部署(CentOS‑Stream‑8)+华为云ECS实操
linux·运维·服务器·centos·华为云·openstack
庵中十三居士2 天前
用nginx代理DeepSeekHarness挂在域名下特定路径提供访问
运维·nginx·deepseek·harness
oyzz1202 天前
CentOS Stream 9 配置静态 IP 口令是多少
linux·tcp/ip·centos
fb_123452 天前
K8s 网络通关手册:Ingress-Nginx 生产级路由 + Calico 三种隧道模式 + NetworkPolicy 白名单实战
网络·nginx·kubernetes