你给站点加了个新服务,让 nginx 监听一个非标准端口(比如 8080、8888 这类)。保存配置、reload、然后——nginx 根本没起来。日志说绑定端口"权限被拒"。你一看,nginx 又不绑定 80 以下的特权端口,怎么会权限不足?而且你以为是"反向代理配置写错了",其实 nginx 连启动都没成功。
现象
- 让 nginx 监听一个非标准端口后,
reload或restart失败; - 报错指向绑定端口时"权限被拒":
[emerg] bind() to 0.0.0.0:8080 failed (13: Permission denied)
- 表面上像是"反代出问题了",但真相是 nginx 压根没启动起来——配置里的服务自然也就全挂了。
关键矛盾在于:nginx 是以 root(或 master 进程)身份绑端口的,而 8080 又是非特权端口(非特权端口不需要 root),按理说不该报权限问题。
根因
根因是 SELinux 对"nginx 能绑定哪些端口"做了标签限制,而它和"是不是特权端口"完全无关。
在 SELinux 的视角里,每个网络端口都带一个类型标签。nginx 所属的域只被允许绑定标签为 http_port_t 的端口。默认情况下,被标记为 http_port_t 的端口只有少数几个——通常就是 80、443、8080、8443 这类众所周知的 HTTP 端口。
当你让 nginx 去监听一个没有被标记为 http_port_t 的端口(比如 8888、9000、某个随机高位端口)时,SELinux 就会在内核层拒绝这次 bind(),并回报 Permission denied。
这就解释了那个矛盾:这跟"是不是特权端口"毫无关系,而是"这个端口有没有被授权给 nginx 使用"。 你可能以为"高位端口谁都能绑",但在 SELinux 眼里,端口是分类型标签管理的,nginx 只能绑它被授权的那一类。
于是我们看到一个非常费时的假象:报错说"权限被拒",看着像反代问题,实际是 SELinux 在端口标签层面拦住了 nginx 的启动。
解决
思路:把这个端口"登记"为 HTTP 端口类型,纳入 nginx 的允许范围。
用 semanage 命令给端口打上 http_port_t 标签:
sudo semanage port -a -t http_port_t -p tcp 8888
参数含义:
-a:新增一条端口映射;-t http_port_t:类型设为 HTTP 端口(nginx 允许绑定的那一类);-p tcp:协议为 TCP;8888:要放开的端口号。
如果该端口已经被别的类型占用了(-a 会报"已存在"),改用 -m 修改:
sudo semanage port -m -t http_port_t -p tcp 8888
查看当前哪些端口是 HTTP 类型,确认改动生效:
sudo semanage port -l | grep http_port_t
放行后,nginx -t 校验配置、再 reload,应当能正常启动。
如果机器上没装 semanage,它来自 policycoreutils-python-utils(或对应发行版的包),先装上:
sudo apt install policycoreutils-python-utils # Debian 系
# 或
sudo dnf install policycoreutils-python-utils # RHEL 系
延伸与预防
这条坑的通用教训是:"权限被拒"有时不是文件权限,是端口标签权限。
可复用的排查习惯:
bind()报Permission denied,先想 SELinux 端口标签,而不是文件权限、也不是"端口被占用"(被占用会报Address already in use)。三者报错不同,别混。- 用
semanage port -l查端口清单,确认目标端口在不在放行列表。 - 同样地,SELinux 对其它资源也按"标签"管理。今天卡的是端口,明天可能是目录(
httpd_sys_content_t)、也可能是布尔值(见反代 502 那类问题)。它们的共性都是:应用被限制在"被标记过的资源"里活动,换一个新资源就要先给它打标签。
一句话:在 SELinux 下,"能用哪个端口"是一份白名单,不在名单上的,即使是高位端口也绑不上。