一次 Nginx 502 问题的深度排查:从 SELinux 到容器网络

作者:donoot

日期:2026-07-07

关键词:Nginx、502 Bad Gateway、SELinux、Docker、反向代理、故障排查


一、背景

我有一台 CentOS 9 服务器,其中运行了一个 Docker 容器 wechat-article-exporter,该容器提供一个 Web 服务,监听在 3000/tcp 端口。为了对外提供安全的 HTTPS 访问,我在宿主机上部署了 Nginx,将外部 13000 端口(HTTPS)的请求反向代理到容器的 3000 端口(HTTP)。

然而,当我通过浏览器访问 https://服务器IP:13000 时,始终收到 502 Bad Gateway 错误,且 Nginx 返回 nginx/1.20.1 版本信息。


二、问题现象

  • 浏览器访问返回 502 Bad Gateway

  • 容器日志显示服务正常启动,监听 0.0.0.0:3000

  • 直接在宿主机上用 curl http://127.0.0.1:3000 可以正常获取页面(200 OK)。

  • Nginx 错误日志中出现大量类似如下记录:

text

复制代码
2026/07/07 14:33:51 [crit] 1328#1328: *71 connect() to 127.0.0.1:3000 failed (13: Permission denied) while connecting to upstream, client: 192.168.1.3, server: _, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:3000/", host: "192.168.1.91:13000"

关键信息:connect() to 127.0.0.1:3000 failed (13: Permission denied)


三、排查过程

3.1 确认容器服务状态

bash

复制代码
sudo docker ps
sudo docker logs wechat-article-exporter

容器正常运行,日志显示 Listening on http://0.0.0.0:3000,说明容器内服务已启动。

3.2 直接访问容器(绕过 Nginx)

bash

复制代码
curl -v http://127.0.0.1:3000

返回正常 HTML,证实容器服务工作正常,问题出在 Nginx 与容器之间的通信。

3.3 检查 Nginx 配置

Nginx 配置如下(精简):

nginx

复制代码
server {
    listen 13000 ssl;
    server_name _;
    ssl_certificate     /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

配置看起来无异常。

3.4 查看 Nginx 错误日志

错误日志明确指出 Permission denied,而不是 Connection refusedConnection timed out。这说明端口可达,但权限不足。

3.5 怀疑 SELinux

CentOS 9 默认启用 SELinux。Nginx 进程通常带有 httpd_tnginx_t 安全上下文,其默认策略可能禁止向其他端口发起网络连接(即使是本地)。

检查 SELinux 布尔值:

bash

复制代码
getsebool httpd_can_network_connect

输出为 off,证实了猜想。


四、根本原因

  • SELinux 策略 :布尔值 httpd_can_network_connect 控制 HTTP 服务(包括 Nginx)是否可以作为客户端连接到任意 TCP 端口。

  • 默认关闭(off),导致 Nginx 无法连接到本机的 3000 端口,即使 127.0.0.1:3000 是监听状态。

  • 直接使用 curl 访问成功,因为 curl 运行在用户 shell 上下文,不受此限制。


五、解决方案

启用 httpd_can_network_connect布尔值并永久保存

bash

复制代码
sudo setsebool -P httpd_can_network_connect 1

验证:

bash

复制代码
getsebool httpd_can_network_connect

输出应为 httpd_can_network_connect --> on

执行后无需重启 Nginx,再次访问网站,502 立即消失,页面正常加载。


六、原理分析

6.1 SELinux 简述

SELinux(Security-Enhanced Linux)是 Linux 内核的安全模块,提供强制访问控制(MAC)。每个进程和文件都有安全上下文(类型),策略定义了它们之间的交互权限。

Nginx 进程的类型通常是 httpd_t(或 nginx_t),其策略允许监听端口、读取静态文件,但默认限制向外发起 TCP 连接,以避免被入侵后作为跳板攻击内网。

6.2 相关布尔值

httpd_can_network_connect 是专门为 HTTP 服务准备的开关:

  • 作用:允许 httpd 相关进程连接到任意远程主机和端口。

  • 默认:关闭(安全考虑)。

  • 风险:开启后 Nginx 可以访问外部网络,但一般场景下(如代理本机端口)风险较低。

如果只允许 Nginx 连接特定端口(如 3000),可以使用更精细的 semanage port 命令将端口加入 http_port_t,但实践上大多数运维直接开启上述布尔值。


七、经验与总结

7.1 排查 502 的通用思路

  1. 确认上游服务是否运行正常(直接访问上游端口)。

  2. 检查 Nginx 错误日志(通常能提供具体原因)。

  3. 检查网络连通性(ping、telnet、nc 等)。

  4. 检查防火墙和 SELinux(CentOS/RHEL 系尤其常见)。

  5. 检查 Nginx 配置中的代理头和超时设置

7.2 预防与建议

  • 在 CentOS/RHEL 系列服务器上部署 Nginx 反向代理时,优先检查 SELinux 布尔值

  • 使用 audit2why 等工具分析 SELinux 拒绝日志,快速定位策略问题。

  • 生产环境建议保持 SELinux 开启,根据需求启用对应布尔值,而非直接禁用 SELinux。

7.3 后续优化

本次排障中还发现容器内部存在一个额外的业务问题(请求 192.168.1.91:443 超时),但这不直接影响 502,仅影响部分 API 功能。根据业务需要,后续可通过修改容器内代码或配置环境变量解决。


八、结语

一次看似简单的 502 错误,却牵涉到 SELinux 这个容易被忽视的环节。希望本文的复盘能帮助读者在遇到类似问题时少走弯路,快速定位并解决问题。

技术栈 :CentOS 9、Nginx 1.20.1、Docker、Node.js、SELinux

核心命令setsebool -P httpd_can_network_connect 1


本文首发于个人技术博客,欢迎交流指正。

相关推荐
Sagittarius_A*3 小时前
Docker:渗透前置容器化核心基础
运维·安全·docker·容器
名字还没想好☜5 小时前
Docker 多阶段构建:把镜像从 1.2GB 砍到 15MB
运维·docker·容器·多阶段构建·镜像优化
難釋懷6 小时前
Nginx浏览器强制缓存
运维·nginx·缓存
生活爱好者!7 小时前
NAS还能玩游戏?部署一款 WebGL 3D 赛车小游戏
游戏·docker·容器·玩游戏
hj2862518 小时前
Docker 核心知识点整理(网络 + Dockerfile + 原理 + 命令)
网络·docker·容器
梦想的颜色16 小时前
【Docker原理】Docker 容器一文打尽:生命周期、环境管控、迁移备份、日志进程排查、垃圾清理
运维·docker·容器·容器生命周期·容器日志排查·容器迁移备份·容器进程管理
ITKEY_19 小时前
macOS brew 安装的nginx 文件在哪里?
运维·nginx·macos
Felix-lxd19 小时前
Ubuntu 22.04 配置 Nginx
linux·nginx·ubuntu
孫治AllenSun20 小时前
【Nginx】配置参数和使用案例
运维·nginx