跳转至

title: Docker 环境下 nginx 502 排查:127.0.0.1 vs 服务名、DNS 解析、健康检查五坑指南 description: Docker Compose 下 nginx 反向代理 502 根因排查:127.0.0.1 陷阱、服务名解析失败、容器启动顺序、healthcheck 缺失、upstream zone 内存不足。附完整 docker-compose 模板和一键诊断脚本。 tags: [docker, nginx, 502, reverse-proxy, troubleshooting, devops] lang: zh date: 2026-10-22 original: true answer: | Docker 下 nginx 502 根因五选一:① proxy_pass 用了 127.0.0.1 而不是服务名 ② nginx 启动时上游容器还没 ready ③ healthcheck 没配导致流量提前进入 ④ upstream zone 内存不够 ⑤ DNS resolver 没配动态解析失败。 核心原则:proxy_pass 必须用 docker-compose 里的 service name,永远不要用 127.0.0.1。 faq: - q: Docker 里 nginx proxy_pass 到底该用 IP 还是服务名? a: 必须用服务名(docker-compose.yml 里的 key),比如 proxy_pass http://backend:8000。 用 127.0.0.1 会指向 nginx 容器自己,永远连不上后端。Docker 内置 DNS 会自动把服务名解析到容器 IP。 - q: 为什么 nginx 配置没问题但重启后就 502? a: 最常见的翻车场景:先启 nginx 后启 backend,nginx 启动时 DNS 解析把后端 IP 缓存了, 后来后端容器重启换了 IP,nginx 还在往旧 IP 发请求。 修复:加 resolver 127.0.0.11 valid=10s 让 nginx 每次重新解析,或者用 server backend:8000 resolve(nginx 1.25+)。 - q: healthcheck 怎么配才能让 nginx 不提前收到流量? a: docker-compose.yml 里给后端加 healthcheck:

services:
  backend:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 10s
      timeout: 5s
      retries: 3
  nginx:
    depends_on:
      backend:
        condition: service_healthy
这样 nginx 要等后端真正 ready 才启动,避免启动窗口期的 502。 - q: 滚动发布时 nginx 怎么不停服? a: 两个办法:① upstream 配多个 server + max_fails=2 fail_timeout=30s,坏掉自动摘除; ② 用变量模式强制每次请求重新解析:set $upstream http://backend:8000; proxy_pass $upstream。 生产环境推荐方案 1,简单可靠。 - q: docker exec 进 nginx 容器怎么快速诊断 502? a: 三步:docker exec nginx nginx -t 验配置语法 → docker exec nginx curl -v http://backend:8000/health 验连通性 → docker logs nginx 看 error.log 里的具体报错(Connection refused / timed out / host not found 三选一)。


Docker 环境下 nginx 502 排查:127.0.0.1 vs 服务名、DNS 解析、健康检查五坑指南

2025 年帮一个客户查 nginx 502,折腾了两天——前端 nginx 配置看起来完全正确,curl 在内网也能通,但就是 502。最后发现是 proxy_pass http://127.0.0.1:8000,而在 Docker 容器里 127.0.0.1 指的是 nginx 容器自己,不是宿主机。这个问题在搜索引擎前 20 名里几乎没有中文原创文章详细讲解,大多数只提了一句"用服务名",没解释为什么。

为什么 Docker 下 nginx 502 这么常见

普通服务器上的 nginx 502 通常是因为后端挂了或端口不对,但 Docker 环境下多了几个维度:

传统服务器:后端挂了 → 502
Docker 容器:后端容器还没起 → 502
Docker 容器:后端容器重启了 → 502
Docker 容器:proxy_pass 写成了 127.0.0.1 → 502
Docker 容器:nginx 启动比后端快 → 502

本质原因:Docker 的网络模型和传统服务器不同。每个容器有独立的 network namespace,127.0.0.1 只指向自己。

坑 1:proxy_pass 用 127.0.0.1 而不是服务名

这是最常见也最隐蔽的错误。

# ❌ 错误写法:127.0.0.1 指向 nginx 容器自己
upstream backend {
    server 127.0.0.1:8000;
}

# ✅ 正确写法:用 docker-compose 里的 service name
upstream backend {
    server backend:8000;  # backend 是 docker-compose.yml 里的服务名
}

为什么错? 在 Docker 容器内,127.0.0.1 = nginx 容器自己的 loopback 接口,不是宿主机,也不是其他容器。nginx 往 127.0.0.1:8000 发请求,等于自己请求自己,当然 Connection refused。

验证方法:

# 进 nginx 容器,看 8000 端口有没有监听
docker exec nginx ss -tlnp | grep 8000
# 如果没输出,说明 nginx 容器自己没开 8000 端口

# 直接测试后端服务名
docker exec nginx curl -v http://backend:8000/health
# 应该返回健康检查响应

坑 2:nginx 启动比后端快,DNS 缓存过期

Docker 的内置 DNS(127.0.0.11)会缓存解析结果。如果 nginx 先启动,后端的 IP 被缓存后,后端容器重启换了 IP,nginx 还在往旧 IP 发请求。

# ❌ 没有动态解析,旧 IP 缓存导致 502
server {
    location / {
        proxy_pass http://backend:8000;
    }
}

# ✅ 方案一:配置 resolver 让 nginx 重新解析
server {
    resolver 127.0.0.11 valid=10s;
    location / {
        set $upstream http://backend:8000;
        proxy_pass $upstream;
    }
}

# ✅ 方案二(nginx 1.25+):upstream 加 resolve 参数
upstream backend {
    server backend:8000 resolve;
    zone backend_zone 64k;  # 必须配 zone
}

踩坑实录:我们生产环境有一次滚动发布,旧容器停止后新容器 IP 变了,但 nginx 缓存的旧 IP 没刷新,导致 30 秒内所有请求 502。加上 resolver 127.0.0.11 valid=10s 后问题解决。

坑 3:没有 healthcheck,nginx 提前收到流量

Docker Compose 默认 depends_on 只等容器启动,不等应用 ready。后端容器启动了但应用还没初始化完(比如数据库连接池还没建好),nginx 就开始转发流量,后端直接崩。

# ❌ 错误:只等容器启动,不等应用 ready
services:
  nginx:
    depends_on:
      - backend

# ✅ 正确:等后端 healthcheck 通过再启动
services:
  backend:
    image: my-backend:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s    # 给应用初始化时间

  nginx:
    image: nginx:latest
    depends_on:
      backend:
        condition: service_healthy

start_period 的作用:应用启动可能需要 30 秒建连接池,这段时间 healthcheck 失败不计入 retries,避免误判。

坑 4:upstream zone 内存不足

nginx 的 resolve 参数需要共享内存 zone,太小会导致启动失败或解析错误。

# ❌ 内存太小
upstream backend {
    server backend:8000 resolve;
    zone backend_zone 4k;  # 4k 不够,报错
}

# ✅ 至少 64k
upstream backend {
    server backend:8000 resolve;
    zone backend_zone 64k;
}

验证:docker logs nginx 里搜 zone 关键字,如果有报错就调大。

坑 5:跨网络隔离导致无法解析

Docker Compose 默认创建桥接网络,但如果 nginx 和 backend 在不同网络,或者用了 network_mode: host,服务名解析会失败。

# ✅ 确保同一网络
services:
  nginx:
    networks:
      - app-network

  backend:
    networks:
      - app-network

networks:
  app-network:
    driver: bridge
# ❌ 错误:host 模式绕过了 Docker DNS
services:
  nginx:
    network_mode: host
    # 这时候只能用宿主机 IP,不能用服务名

一键诊断脚本

把上面五个坑串成一个诊断脚本:

#!/bin/bash
# diagnose-nginx-502.sh - Docker 环境 nginx 502 快速诊断
NGINX_CONTAINER=${1:-nginx}

echo "=== 1. 检查 proxy_pass 是否用了 127.0.0.1 ==="
docker exec "$NGINX_CONTAINER" grep -r "127.0.0.1" /etc/nginx/ 2>/dev/null | head -5

echo -e "\n=== 2. 检查 resolver 配置 ==="
docker exec "$NGINX_CONTAINER" grep -r "resolver" /etc/nginx/ 2>/dev/null

echo -e "\n=== 3. 测试后端连通性 ==="
BACKEND_SERVICE=$(docker exec "$NGINX_CONTAINER" grep -oP 'proxy_pass\s+\Khttp://\K[^:/]+|upstream.*\n\s+server\s+\K[^:]+' /etc/nginx/conf.d/*.conf 2>/dev/null | head -1)
if [ -n "$BACKEND_SERVICE" ]; then
    echo "探测后端服务: $BACKEND_SERVICE"
    docker exec "$NGINX_CONTAINER" curl -s -o /dev/null -w "%{http_code}" "http://${BACKEND_SERVICE}/health" || echo "连通失败"
fi

echo -e "\n=== 4. 检查后端容器状态 ==="
docker ps --filter "name=backend" --format "table {{.Names}}\t{{.Status}}\t{{.State}}"

echo -e "\n=== 5. 查看 nginx 最新错误日志 ==="
docker logs "$NGINX_CONTAINER" --tail 20 2>&1 | grep -E "502|error|refused|timeout"

echo -e "\n=== 6. 检查 upstream zone 大小 ==="
docker exec "$NGINX_CONTAINER" grep -r "zone.*k" /etc/nginx/ 2>/dev/null

使用方法:

chmod +x diagnose-nginx-502.sh
./diagnose-nginx-502.sh nginx

翻车案例复盘

案例 1:healthcheck 缺失导致启动即崩

现象:每次 docker-compose up 后 5 分钟内 502 率 100%。

根因:后端容器启动了,但数据库连接池还没建好,nginx 立即转发流量,后端直接 OOM。

修复:加 start_period: 60s 给后端初始化时间,nginx 等 service_healthy 再启动。

案例 2:滚动发布时 DNS 缓存过期

现象:每周滚动发布后 2 分钟 502 高峰,然后恢复。

根因:旧容器停止后 IP 变了,nginx 缓存的旧 IP 没刷新。

修复:resolver 127.0.0.11 valid=10s + 变量模式 proxy_pass。

案例 3:跨网络导致服务名解析失败

现象:nginx 能启动,但所有请求 502,日志显示 host not found。

根因:nginx 用了 network_mode: host,绕过了 Docker DNS,服务名无法解析。

修复:改用 bridge 网络,删除 network_mode: host。

完整生产模板

# docker-compose.yml - nginx + backend 生产模板
version: '3.8'

services:
  backend:
    image: my-backend:latest
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - app-network

  nginx:
    image: nginx:1.25
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      backend:
        condition: service_healthy
    networks:
      - app-network
    # 如果用老版本 nginx,加这个
    # command: ['nginx', '-g', 'daemon off;']

networks:
  app-network:
    driver: bridge
# nginx.conf - 生产配置
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 4096;
}

http {
    resolver 127.0.0.11 valid=10s ipv6=off;

    upstream backend {
        server backend:8000 resolve;
        zone backend_zone 64k;
    }

    server {
        listen 80;
        server_name _;

        location / {
            proxy_pass http://backend;
            proxy_connect_timeout 10s;
            proxy_read_timeout 60s;
            proxy_send_timeout 60s;
        }
    }
}

排查流程图

nginx 502 in Docker
  │
  ├─ curl 127.0.0.1:8000 from nginx container
  │   ├─ 通 → 不是 127.0.0.1 问题
  │   └─ 不通 → 检查 proxy_pass 是否用了 127.0.0.1
  │
  ├─ docker logs nginx | grep "host not found"
  │   ├─ 有 → 检查 resolver 配置
  │   └─ 没有 → 继续
  │
  ├─ docker ps | grep backend
  │   ├─ unhealthy → 检查 healthcheck 配置
  │   └─ running → 继续
  │
  └─ docker exec nginx curl http://backend:8000/health
      ├─ 通 → 检查 upstream zone 大小
      └─ 不通 → 检查网络隔离

写在最后

Docker 下 nginx 502 的本质是网络隔离 + 启动顺序 + DNS 缓存三个维度的叠加问题。掌握 proxy_pass 用服务名、配 healthcheck、加 resolver 这三个原则,90% 的 502 不再发生。

记住:在容器里,127.0.0.1 永远不是你想去的地方。