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

作者:小小的木头人日期:2026/9/22

背景

在 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 启动失败,端口绑定被拒

现象

1systemctl status nginx.service
2
3nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
4nginx: [emerg] bind() to 0.0.0.0:12000 failed (13: Permission denied)
5nginx: configuration file /etc/nginx/nginx.conf test failed
6

原因分析

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

解决方案

12000 加入 http_port_t 类型:

1semanage port -a -t http_port_t -p tcp 12000
2
3# 验证
4semanage port -l | grep http_port_t
5

如果 semanage 不存在,先安装:

1yum install -y policycoreutils-python
2

然后重启 Nginx:

1systemctl restart nginx
2

备选方案

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

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


问题二:页面 403 Forbidden

现象

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

1"/data/www/index.html" is forbidden (13: Permission denied),
2client: 10.2.2.240, server: localhost, request: "GET / HTTP/1.1",
3host: "10.2.2.240:12000"
4

同时另一条日志:

1connect() to 127.0.0.1:180 failed (13: Permission denied) while connecting to upstream
2

排查过程

1. 先排除认证问题

配置里有:

1satisfy any;
2allow 192.168.34.0/24;
3deny all;
4auth_basic "Restricted Access";
5auth_basic_user_file /etc/nginx/.htpasswd;
6

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

curl 分别测试:

1# 密码错误  401
2curl -v -u admin:admin123 http://10.2.2.240:12000/
3# HTTP/1.1 401 Unauthorized
4
5# 密码正确  403
6curl -v -u admin:admin@123 http://10.2.2.240:12000/
7# HTTP/1.1 403 Forbidden
8

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

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

输出:

1dr-xr-xr-x root   root  /
2drwxr-xr-x root   root  data
3drwxrwxrwx gtdata sftpg www
4
1ls -l /data/www/index.html
2# -rwxrwxrwx. 1 gtdata sftpg 194818 Sep  1 13:57 /data/www/index.html
3

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

3. 锁定 SELinux

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

1getenforce
2# Enforcing
3
4ls -Z /data/www/index.html
5# 上下文不是 httpd_sys_content_t
6

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

解决方案

1# 1. 添加永久上下文规则
2semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"
3
4# 2. 应用规则
5restorecon -Rv /data/www
6
7# 3. 验证
8ls -Z /data/www/index.html
9

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


完整修复清单

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

1# 端口:允许 Nginx 绑定 12000
2semanage port -a -t http_port_t -p tcp 12000
3
4# 文件:给 /data/www 打上 httpd_sys_content_t 上下文
5semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"
6restorecon -Rv /data/www
7
8# 上游:若 Nginx 需要反代本机其他端口,放行出站连接
9setsebool -P httpd_can_network_connect 1
10
11# 重启
12systemctl restart nginx
13

验证:

1curl -v -u admin:admin@123 http://10.2.2.240:12000/
2# HTTP/1.1 200 OK
3

经验总结

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

普通权限用 ls -lnamei -l 一看便知,如果这些都没问题,但日志仍报 Permission denied基本就是 SELinux。用 getenforcels -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 这类上游时,如果没开,会看到:

1connect() to 127.0.0.1:180 failed (13: Permission denied)
2

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 只适合临时验证。生产环境应通过 semanagerestoreconsetsebool 精确放行,既解决问题又不牺牲安全性。


参考


CentOS 7 下 Nginx 访问自定义目录报 403 的完整排查:从端口权限到 SELinux》 是转载文章,点击查看原文


相关推荐


我在 Anthropic ELI5 上加了5样东西,做成了更好用的 ELI5+
深小乐2026/9/14

上篇聊过 Anthropic 都在用的 ELI5 Skill。我用了一阵子,它有它的好,也有绕不过去的问题。 好的地方很明确。整个核心就一句话:给完全不懂的人讲,用 HTML,大图少字。它直接把输出从文字改成画面,一个比喻加几步拆解,概念就立住了,建立直觉特别快。 但用久了,三个问题越来越明显。 1,只有比喻,没有机制。 ELI5 的核心手段就是比喻。比喻天生只讲"像什么",不讲"是什么"。它能让你快速建立直觉,但经不起追问。LLM 比作超级自动补全,你懂了它在"猜下一个词",但解释不了模型为什


zynq中axi_dma的sg模式采集--裸机测试(还有bug)
王明列2026/9/6

参考 嵌入式终端AtShell.csdnzynq中axi_dma的四个实验-第1课:PL搭建.csdnzynq中axi_dma的四个实验-第2课:Vitis裸机测试.csdnzynq中axi_dma的四个实验-第3课:Linux寄存器方案驱动测试.csdnzynq中axi_dma的四个实验-第4课:Linux dmaproxy方案驱动测试.csdnzynq中axi_dma的sg模式采集–PL搭建.csdnzynq中axi_dma的sg模式采集–裸机测试.csdn 约定 PL部分参考


深入理解 Transformer:Transformer 究竟是什么?
渡我白衣2026/8/29

目录 一、先从一张经典结构图开始二、Transformer 不是一个模块,而是一组模块的组合三、什么是 Encoder?四、Encoder Block:Transformer 的基本积木五、第一部分:Multi-Head Self-Attention六、第二部分:为什么 Attention 后面还需要 Add?七、第三部分:Norm 又是什么?八、第四部分:Attention 之后,为什么还需要 FFN?九、现在,我们终于可以看懂 Encoder Block 了十、Encoder 到底输出了什


Go 编程实战:指针 Pointer——理解地址、取址与解引用
程序员爱钓鱼2026/8/21

上一篇我们学习了 Map,到这里已经接触了数组、Slice、Map 等常用数据结构。在继续学习函数之前,还有一个非常重要的基础概念必须掌握——指针 Pointer。 很多刚接触 Go 的开发者看到指针会觉得复杂,其实 Go 的指针相比 C/C++ 简单很多。Go 不支持复杂的指针运算,日常开发主要掌握三个概念即可:变量、内存地址、通过地址访问变量。 理解指针之后,后面学习函数参数、Struct、方法、接口以及并发编程都会更加容易。 一、什么是指针 先看一个普通变量: age := 20 程序


【Linux系统篇】Linux入门基础指令(一)
忧郁兔斯基2026/8/8

在上一篇文章中,我们已经谈过Linux的历史以及如何使用云服务器配置Linux环境和使用Xshell来使用Linux,那么我们在本篇文章中将要开始学习Linux的一些基础指令。 目录 一、关于图形化界面 二、Linux文件以及目录结构 2.1 怎么理解文件 2.2 Linux的文件结构 2.3 Linux的目录结构 2.4 路径 三、简单的指令学习 3.1 pwd 命令 3.2 ls指令 -l 列出文件的详细信息 -a 列出目录下的所有文件 指令选项的组合使用方法 l


500 个服务时快 6 倍:我们如何将 Kibana APM 服务地图从 Canvas 重构到 React DOM
Elasticsearch2026/7/30

作者:来自 Elastic Jenny Pavlova 每个服务节点都会显示告警、 SLO 和异常健康状态,因此你可以仅筛选违反阈值的服务,并将结果嵌入到任何 Kibana 仪表板中,同时支持在整个拓扑中使用完整的键盘导航。 我们基于 React Flow 重构了 Elastic Observability 中的 Kibana APM 服务地图。在拥有 500 个服务时,渲染时间仅为 64ms,相比之前基于 Cytoscape.js 的实现快约 6 倍,同时 JavaScript 体积减少了


京东零售组织新变革:取消两级管理岗,推全栈化开发
AIHR数智引擎2026/7/22

近日,京东零售的组织调整方案流传出来。 很多人第一反应是:大厂又在砍中层。但把方案里的三条细节放在一起看,你会发现这事和"裁员"关系不大,它真正改写的,是"管理者"这三个字的定义。 细节一:C4、C5 两级管理者的"管理者身份"被取消。人还在,但"管人"这顶帽子没了,他们直接挂靠到 C3;以后员工请假、调休,C3 一个人拍板。 细节二:创新零售业务的前端团队并进服务端,全员转做全栈开发。第三季度开始,首批全栈工程师就要直接介入业务。 细节三:C2 序列的团队规模必须大于 50 人,没达标


AI编程出海第一步:别急着写代码,先找到老外真正愿意付费的需求
卷福同学2026/7/14

最近在学AI编程出海的项目,简单说就是做海外网站,让老外们付费使用。而第一步就是要确定网站做什么,也就是找需求 1.伪需求 对于小白来说,可能会想到和AI对话,聊出来一个需求,然后就开始做站了。但是这种AI直接生成的需求,只是看起来可以做,却没考虑到真实用户需求,往往做出来后没人用,或者已有成熟工具站了 2.真实的用户需求 以工具站为例,介绍2种方式来找真实的用户需求 Google Suggest 谷歌自动补齐,填写一个关键词到谷歌搜索,接着在前中后尝试输入a…z,会自动带出搜索词下拉列表,


协程深度解析:明明只有 8 个异步任务,为何 App 线程数瞬间突破 一倍?
潜龙勿用之化骨龙2026/7/6

在 Kotlin 协程中,有一个非常隐蔽但真实存在的问题: 你只是加了 Dispatchers.IO,线程数却变多了,多的不是一点点,而是成倍增长 更关键的是: 👉 这个问题在 Application 启动阶段,比 ViewModel 更严重 0. 先讲清楚“业务真实场景” 为了让问题更贴近真实工程,我们假设一个典型 App: App 启动 & UI 初始化行为 整个应用在启动时,会同时触发两类并发任务: ① Application 启动阶段(全局初始化) 启动 App → 自动


码农的AI翻身(三)你好,我叫 Embedding
Kfaino2026/6/28

AI翻身(三) 你好,我叫 Embedding——AI终于学会了理解,而不是死记硬背 大家好。 我叫 Embedding。 有人叫我: 向量。 有人叫我: 词向量。 还有人喜欢给我起一个特别高大上的名字: 语义空间映射。 听起来很厉害。 其实。 我就是一个翻译。 不过。 我翻译的不是中文和英文。 我翻译的是: 文字和数学。 我第一次见到老板的时候 老板(Transformer)对我说: "以后,人类说什么,你负责翻译。" 我愣住了。 "我不会中文啊。" 老板笑了。 "没关系。" "我也不会

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读