编辑
2025-08-28
Docker
00

目录

Nginx 代理 504?一场由 Docker 与 firewalld 引发的深度网络排查之旅
一、问题的起点:一个简单的 504 Gateway Timeout
二、第一阶段:常规排查与常见误区
1. 检查 Nginx 代理配置
2. 检查后端服务状态
三、第二阶段:深入容器网络,锁定隔离问题
四、第三阶段:决战防火墙,iptables 的“无尽战争”
1. 初探 iptables
2. “核武器”选项的惊人失败
五、第四阶段:锁定真凶,firewalld 与 Docker 的宿命对决
六、最终的、唯一的正确解决方案
结论与反思

Nginx 代理 504?一场由 Docker 与 firewalld 引发的深度网络排查之旅

一、问题的起点:一个简单的 504 Gateway Timeout

故事始于一个看似非常普遍的场景:我部署了一个 Nginx 反向代理服务(Nginx Proxy Manager,运行在 Docker 中),用于代理另一台服务器上的 Dify 应用。

  • 服务器 A:运行 Nginx Proxy Manager (NPM) 的 Docker 容器。
  • 服务器 B:运行 Dify 应用,监听在 [Server_B_IP]:80症状
  • 通过浏览器直接访问 http://[Server_B_IP],一切正常。
  • 通过手机等外部网络访问 http://[Server_B_IP],也一切正常。
  • 通过配置好的域名 your.domain.com 访问,Nginx 代理返回 504 Gateway Timeout。 这通常意味着代理服务器(NPM)在规定时间内没有从上游服务器(Dify)收到响应。但既然 Dify 服务本身是好的,问题显然出在从“服务器 A”到“服务器 B”的连接路径上。

二、第一阶段:常规排查与常见误区

按照标准流程,我们首先排查了最常见的可能性。

1. 检查 Nginx 代理配置

  • proxy_pass 地址:确认 NPM 中配置的目标地址是正确的 http://[Server_B_IP]:80
  • 超时设置:检查 proxy_read_timeout 等超时参数,确认时间足够长。
  • 请求头 (proxy_set_header):确保 Host 等关键请求头被正确传递。 结论:配置从逻辑上看没有问题。

2. 检查后端服务状态

我们已经通过外部网络直接访问验证了 Dify 服务是健康且可达的。 阶段性结论:问题不在应用层,而在网络层。Nginx 配置和服务本身都是好的,但 NPM 容器本身无法连接到 Dify 服务器

三、第二阶段:深入容器网络,锁定隔离问题

为了验证“容器无法连接”的猜想,我们进行了决定性的一步:

bash
# 登录 NPM 所在的服务器 A # 在【宿主机】上测试连接 ping [Server_B_IP] # 成功 telnet [Server_B_IP] 80 # 成功 # 进入【NPM 容器内部】测试连接 docker exec -it [npm_container_id] /bin/bash curl -v http://[Server_B_IP]:80 # 失败!卡在 "Trying [Server_B_IP]:80..."

关键线索出现了! 宿主机可以访问目标,但运行在宿主机上的 Docker 容器却不能。这 100% 证实了问题在于 Docker 容器的网络环境被隔离,无法访问外部网络

四、第三阶段:决战防火墙,iptables 的“无尽战争”

既然是网络隔离,那么嫌疑人自然是防火墙。

1. 初探 iptables

我们首先尝试手动向 iptables 添加规则,试图“打通”容器的出站流量。

bash
# 尝试1:在 DOCKER-USER 链添加规则 iptables -I DOCKER-USER -s [container_subnet] -j ACCEPT # 结果:失败。 # 尝试2:在 FORWARD 链强行插入规则 iptables -I FORWARD 1 -s [container_subnet] -j ACCEPT # 结果:依然失败。

iptables 的直接修改全部失效,这极不寻常。

2. “核武器”选项的惊人失败

为了最终确认问题,我们使用了最极端的方法:临时放开所有流量转发

bash
iptables -P FORWARD ACCEPT

这条命令会将 FORWARD 链的默认策略从 DROP (丢弃) 改为 ACCEPT (接受),理论上应该能解决所有转发问题。 然而,在容器内执行 curl依然失败! 最终诊断的转折点:连 iptables 的默认策略都无法生效,这说明有一个更高层级的防火墙管理工具正在控制着整个网络,它动态地管理着 iptables,使得我们的手动修改被忽略或覆盖。

五、第四阶段:锁定真凶,firewalld 与 Docker 的宿命对决

在 CentOS/RHEL 等系统中,这个更高层级的工具通常是 firewalld

bash
systemctl status firewalld # 输出: active (running)

真相大白firewalld 服务正在运行。Docker 也会尝试修改 iptables 来配置自己的网络。当 firewalld 和 Docker 同时管理 iptables 时,极易产生规则冲突和不可预测的网络状态,导致 Docker 容器网络“瘫痪”。 我们之前在 firewalld 状态中看到的 WARNING: COMMAND_FAILED 日志,也佐证了 firewalld 启动时就与 Docker 的规则发生了冲突。

六、最终的、唯一的正确解决方案

对于这种级别的服务冲突,修修补补已无济于事。社区和官方推荐的唯一可靠方案是:二选一。既然我们依赖 Docker 的网络功能,就必须让 firewalld “让位”。

  1. 停止并禁用 firewalld 服务
    bash
    # 停止服务 systemctl stop firewalld # 禁用开机自启 systemctl disable firewalld
  2. 重启 Docker 服务 这是至关重要的一步。在 firewalld 关闭后,重启 Docker 会让它在一个“干净”的环境下,重新生成所有它需要的、正确的 iptables 规则,而不再受到任何干扰。
    bash
    systemctl restart docker
  3. 验证 此时再次进入 NPM 容器内部测试:
    bash
    docker exec -it [npm_container_id] curl -v http://[Server_B_IP]:80 # * Trying [Server_B_IP]:80... # * Connected to [Server_B_IP] (Server_B_IP) port 80 (#0) # ... # < HTTP/2 302
    连接成功!问题解决。

结论与反思

这次排查过程曲折而深刻,最终的结论是:

**在部署 Docker 的 Linux 服务器上,如果遇到容器无法访问外部网络的诡异问题,请