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
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
一键诊断脚本
把上面五个坑串成一个诊断脚本:
#!/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
使用方法:
翻车案例复盘
案例 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 永远不是你想去的地方。