跳转到内容

Nginx 502/504 怎么修?后端、上游超时、证书 6 步排查 ​

网站又 502 了。这不是第一次——上次你一句 systemctl restart nginx 就救回来了,可这次重启之后只好了几分钟,页面又开始报 502 Bad Gateway,过一会儿变成 504 Gateway Time-out。日志里 connect() failed 和 upstream timed out 交替出现,Nginx 进程明明还活着,配置文件也没人动过。

重启只能救一时,因为它从头到尾没有回答那个最该问的问题:到底是谁没响应?502 和 504 都说明,Nginx 作为网关没有拿到上游的正常响应,但它们的病因并不相同——502 多发生在连接上游或读取响应的早期,504 更常见于上游没在规定时间内完成。换句话说,每次重启 Nginx 都是在一味地打地鼠:打掉的是眼前这个表象,真正的病根要么在配置里,要么在后端进程,要么在网络那一层,根本没被碰到。

这篇文章给一条能在生产服务器上按顺序执行的 6 步排查路径:先确认错误和时间范围,再确认后端是否活着,然后核对监听地址与代理配置、区分连接失败和响应超时,最后检查网络与 HTTPS。每一步都强调先观察、再做最小修改,不要一上来就删配置、重装 Nginx 或清空日志。顺着日志把「没响应」的那一环揪出来,比再喊一次「重启一下试试」有用得多。

第 1 步:先确认是 502、504,还是另一层返回的错误 ​

先从客户端和服务器各看一次。客户端看到的页面不一定由当前这台 Nginx 生成,也可能来自 CDN、负载均衡器或另一层反向代理。确认影响范围能避免在错误的机器上排查:

bash
curl -I https://example.com
curl -sS -o /dev/null -w 'code=%{http_code} total=%{time_total}s\\n' https://example.com/

sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/nginx/access.log

重点看同一时间段的 error log。常见线索包括:

  • connect() failed (111: Connection refused) while connecting to upstream:Nginx 找到了目标地址,但目标端口没有接受连接。
  • upstream timed out ... while connecting to upstream:建立上游连接就已经超时,方向通常是地址、路由、防火墙或服务负载。
  • upstream timed out ... while reading response header from upstream:连接已经建立,但后端迟迟没有返回响应头,更像是应用慢、数据库卡住或超时时间不匹配。
  • no live upstreams:配置中的上游没有可用节点,常见于 upstream 健康状态或服务发现问题。

不要只看最后一行日志。使用 grep 把一次故障的时间窗口捞出来,并把 request id、上游地址和 URI 一起保存,后面才能把 Nginx 日志与应用日志对上:

bash
sudo grep '25/Aug/2026:02:3' /var/log/nginx/error.log
sudo journalctl -u nginx --since '10 minutes ago' --no-pager

如果 curl 访问的是 CDN,而你直接访问源站正常,问题应转向 CDN 到源站的连接、回源证书或安全组,不要继续改 Nginx 的代理超时。

故障窗口较长时不要一行行读日志:在终端里把关键字高亮出来(见终端关键字高亮),connect() failed、upstream timed out、no live upstreams 会立刻跳出来,直接指向真正沉默的那一环。

第 2 步:确认后端进程和监听端口 ​

502 最常见的根因不是 Nginx 配错,而是应用进程已经退出、启动失败,或者应用监听在另一个端口。先看服务状态,再看端口:

bash
sudo systemctl status myapp --no-pager
sudo journalctl -u myapp --since '15 minutes ago' --no-pager
sudo ss -lntp

如果应用通过 Docker 或 Compose 运行,额外检查容器状态和端口映射:

bash
docker compose ps
docker compose logs --tail=100 app
docker port app

然后从 Nginx 所在的网络命名空间直接访问上游。假设配置写的是 127.0.0.1:3000:

bash
curl -v --max-time 5 http://127.0.0.1:3000/health

这一步的结果很关键:

  • Connection refused:目标主机可达,但端口没有服务监听。启动或修复后端,或者改回正确端口。
  • Operation timed out:不一定是进程退出,可能是容器网络、路由或防火墙问题。
  • 返回 200、401 或应用自己的 500:上游至少能响应,继续检查 Nginx 的 proxy_pass、请求头、路径和超时。
  • 本机访问正常,Nginx 访问失败:检查 Nginx 是否在容器、chroot 或其他网络命名空间中;127.0.0.1 指向的是 Nginx 自己所在的环境,不一定是宿主机。

如果健康检查接口正常,但真实请求 502,不要只盯着进程。可能是某个请求触发了应用崩溃、上游连接数耗尽,或 Nginx 代理的路径与应用路由不一致。

第 3 步:核对 proxy_pass、协议、路径和 DNS ​

打开实际生效的配置,而不是凭记忆看某个备份文件:

bash
sudo nginx -T > /tmp/nginx-effective.conf
sudo less /tmp/nginx-effective.conf

检查对应 server 和 location 中的 proxy_pass。常见错误有四类:

  1. 端口写错:应用实际监听 8080,配置却指向 3000。
  2. 协议写错:上游只提供 HTTP,却写成 https://;或者上游要求 TLS,Nginx 却用明文连接。
  3. 路径拼接不符合预期:proxy_pass http://backend; 与 proxy_pass http://backend/; 在带 URI 的 location 中可能产生不同的转发路径。
  4. 容器内 DNS 不可解析:配置使用服务名时,Nginx 与后端不在同一个 Docker network,导致 host not found in upstream。

修改前先用和 Nginx 相同的主机环境验证:

bash
getent hosts backend
curl -v http://backend:8080/health
sudo nginx -t

nginx -t 通过只表示语法和引用文件基本可读,不代表上游一定能连通。只有测试通过后才 reload:

bash
sudo systemctl reload nginx

优先使用 reload 而不是 stop/start。reload 会让旧 worker 完成已有请求,减少不必要的中断;但如果后端仍然不存在,reload 当然不会凭空修复 502。

第 4 步:把 502 的“连不上”与 504 的“等不到”分开 ​

504 通常意味着请求已经进入代理流程,但上游没有及时完成。此时不要简单地把 proxy_read_timeout 从 60 秒改成 600 秒:如果真正原因是数据库锁、死循环或下游 API 卡死,你只是让连接占用更久,并把故障放大。

先确认日志里的超时阶段,再查看应用请求耗时、数据库连接池、CPU、内存和网络:

bash
free -h
uptime
top -b -n 1 | head -25
sudo ss -s

如果应用确实需要长时间生成报表或上传文件,应在业务上把任务改成异步,并让前端轮询任务状态。对于已经确认合理的同步请求,再按接口位置设置有边界的超时:

nginx
location /api/report/ {
    proxy_pass http://app_backend;
    proxy_connect_timeout 5s;
    proxy_send_timeout 30s;
    proxy_read_timeout 120s;
}

几个参数不要混为一谈:proxy_connect_timeout 控制建立连接的等待时间;proxy_send_timeout 针对向上游发送请求;proxy_read_timeout 针对两次读取上游响应之间的等待。提高读取超时不会修复“端口没人监听”的 502,也不会解决应用本身崩溃。

同时检查请求体大小和上传场景。上传被 Nginx 拒绝通常是 413,不是 504;但上传链路中上游处理过慢,仍可能表现为超时。把问题对应到日志阶段,比复制一段“万能 Nginx 配置”可靠得多。

第 5 步:检查网络、资源和 upstream 节点 ​

当后端在另一台机器、容器或 Kubernetes 服务中时,本机健康检查并不能证明 Nginx 到它的链路正常。逐层验证 DNS、TCP、HTTP:

bash
getent hosts api.internal.example
nc -vz -w 3 api.internal.example 8080
curl -v --connect-timeout 3 --max-time 10 http://api.internal.example:8080/health

根据结果检查安全组、iptables、云网络路由、Docker network 以及服务发现记录。若使用多个 upstream,确认所有节点都真的提供同一个协议和路径:

nginx
upstream app_backend {
    server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:8080 max_fails=3 fail_timeout=30s;
}

不要为了“先恢复”随意删除失败节点。先在每个节点执行同样的 health check,再决定是修复节点、摘除节点,还是调整容量。还要留意连接数、文件描述符和内存:后端在资源耗尽时可能既能接受健康检查,又无法处理真实请求。

如果上游只能通过跳板机或本地转发的端口访问,把这条链路记录下来并沿用同一套命令验证(见端口转发),下一位值班同学就能复现你的检查,而不是靠猜拓扑。

远程操作时,建议把命令、日志和配置检查集中在一个可复现的会话里;使用 Termark 连接服务器时,可以在终端中保留诊断上下文,并在需要时通过 SFTP 查看配置或日志文件。它解决的是操作路径,不会替你判断哪个后端应该被重启。

第 6 步:最后检查 HTTPS 与证书链 ​

HTTPS 有两条独立链路:客户端到 Nginx,以及 Nginx 到 HTTPS 上游。客户端证书过期、域名不匹配时,浏览器通常会直接提示 TLS 错误;Nginx 连接 HTTPS 上游失败,则会在 error log 中看到 SSL handshake、certificate verify 或协议错误,外观上可能被误认为 502。

bash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

curl -vk https://backend.internal.example/health

确认:证书尚未过期、SAN 包含访问域名、完整证书链已部署、Nginx 配置的 proxy_ssl_server_name 与上游要求一致。不要为了绕过生产问题永久关闭证书验证;那会把连接问题变成安全问题。

一份不容易误操作的排查清单 ​

bash
# 1. 客户端状态与 Nginx 日志
curl -I https://example.com
sudo tail -n 100 /var/log/nginx/error.log

# 2. 后端状态与监听
sudo systemctl status myapp --no-pager
sudo ss -lntp

# 3. 从当前环境直连上游
curl -v --max-time 5 http://127.0.0.1:3000/health

# 4. 查看生效配置并校验
sudo nginx -T > /tmp/nginx-effective.conf
sudo nginx -t

# 5. 只在确认原因后 reload
sudo systemctl reload nginx

顺序很重要:先用日志确定故障阶段,再直连后端;后端不可达时修后端或网络,后端可达时再查代理配置;确认只是响应慢后,才评估业务或超时设置;最后再处理证书和多节点问题。每次只改一件事,并记录 reload 前后的错误日志,回滚会容易很多。如果故障发生在凌晨,先止住影响再留下证据:保存错误日志、有效配置、后端状态和变更时间;白天再补健康检查、资源告警、异步任务和合理超时,下一次告警到来时你面对的就是可定位的问题,而不是一页只能反复刷新的 502。

参考资料 ​

Termark · SSH 客户端与终端工作台