跳到主要内容

排查 nginx 反向代理 502/504 错误

· 阅读需 4 分钟
维护者

把这个博客部署起来之后,访问偶发 502、504。前端就一个静态站点,后端就一个 nginx 容器,链路很简单,但越是简单的链路,问题越容易被忽略。记一下这次排查的过程。

环境

  • 浏览器 → 192.168.1.3 上的 nginx-ui(反代,终止 TLS)
  • nginx-ui → 192.168.1.5:8080 上的容器(nginx 托管静态文件)

偶发,不是必现,大概十次里一两次。

现象

  • 大部分请求正常 200。
  • 偶发 502 Bad Gateway,或 504 Gateway Timeout。
  • 刷新一下往往就好了。

排查思路

502/504 的本质是「反代拿不到上游的有效响应」。要么上游没响应(504 超时),要么上游拒绝了连接 / 连接被重置(502)。所以排查分两条线:

  1. 上游本身是不是活的——容器 nginx 在不在、监听对不对。
  2. 反代到上游的网络通不通——连接能不能建立、建立后能不能在超时内完成。

第一步:确认上游存活

# 容器状态
docker ps | grep www-anderslane

# 容器内 nginx 监听
docker exec www-anderslane wget -qO- http://localhost:80/ | head -5

# 从反代机直接打上游
curl -v -H 'Host: www.anderslane.cn' http://192.168.1.5:8080/

结果:容器在,监听正常,从反代机 curl 上游也 200。说明上游是活的,问题出在「偶发」上。

第二步:看反代错误日志

# nginx-ui 容器的错误日志
docker logs <nginx-ui-container> 2>&1 | grep -E 'upstream|502|504'

抓到关键几行:

upstream timed out (110: Connection timed out) while reading response header from upstream
upstream prematurely closed connection while reading response header from upstream

两类错误:

  • Connection timed out:TCP 连上了,但上游迟迟不回响应头 → 504。
  • upstream prematurely closed connection:连接建立后被上游主动关了 → 502。

第三步:上游资源排查

容器是静态文件服务,怎么会超时?看容器资源:

docker stats --no-stream www-anderslane

发现偶发时段 CPU 飙到接近 100%,内存也接近上限(docker-compose.yml 里限了 256m)。再看进程:

docker exec www-anderslane ps aux

nginx worker 之外,多了一堆 wget --spider 进程——那是 healthcheck。

根因

docker-compose.yml 的健康检查:

healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:80/"]
interval: 30s
timeout: 5s
retries: 3

wget --spider 在 alpine 上实际会发起请求并解析响应,30s 一次本不算频繁。但 mem_limit: 256m 偏紧,nginx 在高并发或被健康检查挤占时,worker 偶发 OOM 被杀、连接被重置 → 反代侧表现就是 502/504。

两条因素叠加:

  1. 内存上限太紧,峰值时 worker 被杀。
  2. 健康检查和正常请求抢资源,放大了峰值。

处理

  1. 放宽内存上限到 512m,给 nginx worker 留余量。
  2. 健康检查换成更轻的探测,直接打一个静态小文件,少解析。
  3. 反代侧 proxy_read_timeout 设 30s(本来就是),proxy_connect_timeout 设 5s 快速失败。
  4. 给静态资源开 expires 长缓存,减少回源压力。

改完观察两天,502/504 消失。

复盘

几条经验:

  • 偶发 502/504 先看上游资源,别一上来就调反代超时。超时只是症状,根因往往是上游扛不住。
  • 容器资源限制要留余量,256m 对 alpine + nginx 平时够用,但峰值(OOM 边缘)会偶发杀进程,很难复现。
  • 健康检查本身也是负载,在资源紧张的容器里别用重检查。
  • 日志是最快的线索upstream timed out vs prematurely closed 区分了超时和重置,直接指向不同方向。

排查这类问题的顺序我习惯是:上游存活 → 上游资源 → 网络/超时 → 反代配置。前两步排掉,剩下的基本就是配置调优了。