作者: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 refused 或 Connection timed out。这说明端口可达,但权限不足。
3.5 怀疑 SELinux
CentOS 9 默认启用 SELinux。Nginx 进程通常带有 httpd_t 或 nginx_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 的通用思路
-
确认上游服务是否运行正常(直接访问上游端口)。
-
检查 Nginx 错误日志(通常能提供具体原因)。
-
检查网络连通性(ping、telnet、nc 等)。
-
检查防火墙和 SELinux(CentOS/RHEL 系尤其常见)。
-
检查 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
本文首发于个人技术博客,欢迎交流指正。