你配好了 nginx 反向代理,把请求转发给本机的后端服务。用 curl 直接访问后端端口,一切正常;可一旦走 nginx 进来,就整齐地返回 502 Bad Gateway。你把 nginx 配置检查了好几遍,upstream 地址没错、端口没错,后端也明明活着——那这个 502 到底从哪来?
现象
- 直接访问后端服务:正常响应;
- 经 nginx 反代访问:清一色返回 502;
- nginx 的
error.log里能看出点端倪,但表面配置全部正确:
[error] connect() failed (13: Permission denied) while connecting to upstream
注意这一行里的关键信息:(13: Permission denied)。不是"连接被拒绝"(Connection refused),而是"权限被拒"。 后端根本没在拒绝你,是内核层面拦住了 nginx 的连接动作。
根因
根因是 SELinux 的一个布尔值把访问挡在了内核层。
在启用了 SELinux(尤其是 enforcing 模式)的发行版上,nginx 运行在一个受限的安全域里。这个域默认被策略限制:不允许主动发起对外网络连接。也就是说,即使 nginx 的配置完全正确、目标服务就在本机,内核仍然会因为"nginx 所在的域没有发起网络连接的授权"而拒绝这次 connect(),并回报 Permission denied。
控制这个开关的,是一个叫 httpd_can_network_connect 的 SELinux 布尔值,它默认是 关闭 的。关闭状态下,任何让 nginx 主动连出去的请求——不管目标是本机、内网还是外网——都会被拦。
为什么表面看不出原因?因为 SELinux 拦的是"合法但未授权"的行为,而且它不在应用层报错。nginx 只知道自己"连不出去",它报的是一个通用的连接失败;真正的原因藏在安全子系统的日志里(/var/log/audit/audit.log 或 ausearch),普通应用日志里看不到。于是配置正确、服务活着、却 502,成了这类问题的典型表现。
解决
打开那个布尔值开关,并让设置持久化(-P 表示写入策略、重启后仍生效):
sudo setsebool -P httpd_can_network_connect on
含义拆开看:
httpd_can_network_connect:允许 httpd(及其派生服务,nginx 在其许可范围)发起网络连接;-P:持久化写入策略文件,否则重启就恢复;on:打开。
改完不需要重启 nginx,立刻生效,再访问应当恢复正常。
如果关心"是谁在拦",可以从审计日志确认,别靠猜:
sudo ausearch -m avc -ts recent
# 或
sudo grep denied /var/log/audit/audit.log | tail
你会看到类似 avc: denied { name_connect } for ... httpd_t ... 的记录,直接指认 SELinux 就是拦截方。用审计证据确认,而不是看到 502 就盲目改配置。
延伸与预防
这条坑的通用教训是:SELinux 会拦住"合法但未授权"的网络访问,而且不会在应用层明说。
可复用的排查思路:
- 看到
Permission denied(而不是Connection refused),优先怀疑安全子系统,而不是后端或配置。连接被拒是"对方不接",权限被拒是"你没被授权去连"——方向完全不同。 - 先在 SELinux 日志里找
avc: denied。这是判断"是不是 SELinux 拦的"最直接的证据。也可以临时setenforce 0验证:若问题立刻消失,基本坐实是 SELinux。但别长期关 SELinux,它是安全防线,正确做法是放开特定授权,而不是整个关掉。 - 记住几个常见开关。反代要连后端 →
httpd_can_network_connect;要连数据库 →httpd_can_network_connect_db;要发邮件 →httpd_can_sendmail。遇到 nginx/后端联动出权限问题,先查这几个布尔值。
一句话:当"配置明明对、服务明明活"却报权限被拒时,把视线从应用层抬到内核安全层。