当你看到 504 Gateway Time-out,网关其实是在说:我去上游要数据,等太久了

你在浏览器里遇到的 504 Gateway Time-out 报错,来自 HTTP 协议的状态码体系。它表示:当前为你服务的这一跳服务器处在网关或代理的位置,它把你的请求转发到了上游应用或上游网关,但在约定的等待时间内没有等到上游返回,于是只好回一个 504 给客户端。IETF 的最新版语义规范把它定义得非常清楚:当服务器充当网关或代理,却未能在规定时间内收到其必须访问的上游服务器的响应,就应该返回 504。这与 502 Bad Gateway 的差异在于:502 是网关从上游收到了一个不合法或错误的 HTTP 响应,而 504 是干脆没在超时前等到有效响应。(IETF Datatracker, MDN Web Docs)

把请求的旅程铺开看更容易理解:浏览器 → CDN/WAF → 负载均衡器 → 反向代理(如 Nginx/HAProxy/Apache httpdmod_proxy)→ 应用服务器(Node.js/Python/PHP/Java/Go 等)→ 数据库或外部服务。只要在某一跳,等待上游的时间超过了该跳的超时设置,这一跳就会放弃等待并以 504 告终。例如 Nginxproxy_read_timeout 默认是 60s,如果在两次读取上游数据之间超过这个时间没有任何字节到达,连接会被关闭,最终返回 504。(Nginx)

CDN 或云负载均衡也会产生类似含义的 504。比如 AWS Application Load BalancerHTTP 504 的常见成因包括:在连接超时窗口内无法与目标实例建立连接、建立连接后在空闲超时内未收到响应、或网络访问控制配置阻断了回程端口。AWS CloudFront 也在转发到源站超时或源站直接返回 504 时呈现该状态码。(AWS Documentation)

Cloudflare 一类的边缘代理也会在无法与源站建立联系或源站响应过慢时返回 504,其官方故障文档明确把 502/504 归为与源站通信失败的大类。(Cloudflare Docs)


这类超时到底由谁决定、为什么会发生

不同组件有各自的超时钟表:

  • 反向代理的读取超时。例如 Nginx 使用 proxy_read_timeout 控制从上游读取响应的等待时间,默认 60s。这不是整个响应的总时长限制,而是两次读操作之间的空闲间隔限制;如果上游持续输出数据,连接可以长时间保持。(Nginx)
  • 负载均衡器的连接与空闲超时。ALB 典型情况下,连接建立阶段等候有限的秒数,连接建立后还受空闲超时控制,超过空闲时间没流量就会断开并可能触发 504。(AWS Documentation)
  • 代理或应用的长连接策略。比如消息推送、WebSocketSSE 这种长连接,若没有心跳或上游保持活跃,默认 60s 的空闲超时就可能使连接被网关断开。Nginx 指南也建议在这类场景提高 proxy_read_timeout 或让上游定期发送心跳帧。(Nginx)
  • 其他网关的等待策略。HAProxy 手册与常见问题中也提示:当等待后端响应超时,会返回 504,需要结合日志与 timeout servertimeout connect 等配置分析与调整。(HAProxy Technologies)

一旦某一层的等待钟走到了尽头,而上游仍未送回头部或数据,就会触发 504。原因可能是上游真的慢(CPU 顶满、GC 卡顿、DB 查询慢、锁等待、线程池耗尽)、也可能是网络路径不通或安全组/ACL 设置让回程报文发不回来,还可能是上游持续工作但长时间没有任何字节到达代理的读缓冲,导致被判定为超时。(AWS Documentation)


作为访问者,可以做的快速甄别

当页面抛出 504,很大概率并不是你的浏览器或本地网络的锅,而是站点一侧各层之间的通信出问题了。你可以先轻量排查:刷新、换个网络、关代理/VPN、稍后再试;若问题持续且只发生在某个特定网站,通常需要网站维护方处理。MDN 与多家运维文档都强调,504 多半与服务器端超时相关。(MDN Web Docs, Kinsta®)


作为站点维护者,如何系统地定位与修复

把它当作一次跨越多跳的延迟问题,从外到内逐层压缩不确定性。

观测与证据

  • 在代理与负载均衡层启用访问与错误日志,记录上游地址、响应时间、状态。Nginx 的错误日志里常见 upstream timed out ... while reading response header from upstream 的字样,基本就是上游没有在时限内送回响应头。(Nginx)
  • ALBCloudFront 查看 ELB 访问日志、CloudWatch 指标,核对 TargetResponseTimeELB 级别的 HTTP 504 计数峰值与实例侧负载。(AWS Documentation)
  • 对上游应用做端到端链路追踪与慢事务分析,确认是否存在极端长尾请求。

连通性与配置

  • curlmtr/tracerouteping 在代理与上游之间做连通性与往返延迟测试,排除网络层面的丢包或路由异常。实践性指引也建议优先确认网络路径健康。(Netdata)
  • 检查云环境的安全组与 NACL,确保负载均衡节点的临时端口范围允许回程,AWS 文档列举了这条常见误配置。(AWS Documentation)
  • 审核代理的超时设置是否与业务相匹配。长时间导出、视频转码、复杂报表这类本就运行较久的请求,要么提高等待窗口,要么改造为异步任务。Nginxproxy_read_timeoutproxy_connect_timeoutHAProxytimeout servertimeout connectApacheProxyTimeout 均需与应用时长对齐。(Nginx, HAProxy Technologies, Server Fault)

应用与后端性能

  • 识别慢查询、外部依赖的长尾调用,给数据库与下游接口加超时与重试上限,必要时设置断路器,避免把所有线程绑死在不可预期的等待上。
  • 增加并发能力与容量:连接池、线程池、worker 数、实例水平扩展。
  • 对长轮询与长连接类业务,使用心跳、分片响应或升级为 WebSocket/SSE 并匹配更合适的空闲超时。(Nginx)

为什么你看到的是 504 而不是 502/503

这一组 5xx 常让人困惑。MDN 的解释可以简明对比:504 是没等到上游的响应;502 是收到了,但格式或协议上不对;503 是服务暂时不可用,常由限流或维护导致。这三者定位入口不一样:504 优先看链路延迟与超时配置,502 看协议与网关转换,503 看容量与熔断。(MDN Web Docs)


不同平台的一点差异

现实世界里,边缘层对超时的命名和细节有差异。CloudFront504 既可能是源站返回的 504 被透传,也可能是边缘等待源站超时自行生成;ALBHTTP 504 可能来自连接阶段或空闲阶段的超时;Cloudflare 也把 502/504 统称为与源站通信失败,排查时需要对照各自的日志与指标来识别归因。(AWS Documentation, Cloudflare Docs)


可运行的小示例:用一段 Node.js 代码模拟 504

很多人没有 NginxHAProxy 的环境,也能用一段小脚本快速体会 504 的本质:网关等不到上游。下面这段代码一举两用:

  • http://localhost:9000/?delay=ms 提供一个故意延迟的上游服务;
  • http://localhost:8000/ 提供一个简单代理,向上游转发并设置 2s 超时;
    当你把上游延迟调到 3000ms,代理会在 2s 到点时自制一个 504 返回。

代码只用单引号,避免出现英文双引号;粘贴成 demo.js 后用 node demo.js 运行。

// demo.js
const http = require('http');
const url = require('url');

const upstream = http.createServer((req, res) => {
  const q = url.parse(req.url, true).query;
  const delay = Number(q.delay || '0');
  setTimeout(() => {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ ok: true, delay }));
  }, isNaN(delay) ? 0 : delay);
});

upstream.listen(9000, () => {
  console.log('upstream on :9000');
});

const proxy = http.createServer((clientReq, clientRes) => {
  const options = {
    hostname: '127.0.0.1',
    port: 9000,
    path: clientReq.url,
    method: clientReq.method,
    headers: clientReq.headers
  };

  const upstreamReq = http.request(options, upstreamRes => {
    clientRes.writeHead(upstreamRes.statusCode || 200, upstreamRes.headers);
    upstreamRes.pipe(clientRes);
  });

  // 2s 没有拿到上游响应头就超时,模拟网关的等待钟
  upstreamReq.setTimeout(2000, () => {
    upstreamReq.destroy(new Error('upstream timeout'));
  });

  upstreamReq.on('error', err => {
    if (!clientRes.headersSent) {
      clientRes.writeHead(504, { 'Content-Type': 'text/plain' });
    }
    clientRes.end('504 Gateway Time-out: ' + err.message);
  });

  clientReq.pipe(upstreamReq);
});

proxy.listen(8000, () => {
  console.log('proxy on :8000, try:');
  console.log('  fast:  curl "http://127.0.0.1:8000/?delay=500"');
  console.log('  slow:  curl "http://127.0.0.1:8000/?delay=3000"');
});

运行后:

  • curl 'http://127.0.0.1:8000/?delay=500' 能得到上游的 200
  • curl 'http://127.0.0.1:8000/?delay=3000' 会返回 504 Gateway Time-out: upstream timeout

这个小实验揭示了核心事实:只要网关等待上游超过了自己的超时策略,就会触发 504——无论上游是不是过一会儿才慢慢把结果算出来。


真实环境中的典型修复清单

把上面的分析落地成可执行动作:

  1. 把慢操作改造成异步
    报表导出、压缩、视频转码、跨系统聚合等,改为作业队列与回调/轮询式查询状态。网关的等待只负责快速确认作业受理。

  2. 为上游和下游都设置合理的超时
    数据库客户端、HTTP 下游调用要有明确的连接超时与读取超时,还要设置重试上限,避免在网关之外形成更长的隐性等待链。

  3. 同步长连接的存活策略
    SSEWebSocket 的心跳频率要覆盖各层默认空闲超时;或在代理层提升空闲超时上限。Nginx 官方文档明确指出,默认 60s 的空闲期会关连接,可以用 proxy_read_timeout 增大,或由上游定时发送心跳。(Nginx)

  4. 按需调高代理与负载均衡时钟
    示例:Nginxproxy_read_timeout 300s;HAProxytimeout server 5mApache httpdProxyTimeout 300;注意提升时限只是争取时间,不是替代性能优化。(Nginx, HAProxy Technologies, Server Fault)

  5. 扩容与隔离
    把耗时接口拆分到独立后端或队列消费者,给它单独的池与伸缩规则,避免拖慢其他接口;在 ALB/NGW 层按目标特性设置独立监听与规则。AWS 的文档同样提示了连接与空闲时限的影响。(AWS Documentation)

  6. 网络与安全配置一致性
    严格核对安全组、NACL、防火墙与回程端口范围,确保存活探针、回源端口都能互通;AWS 的知识库特别提到对临时端口的放行。(AWS Documentation)


当你用 NginxHAProxyApache 时的参考片段

  • Nginx:在反代 locationserver 上设定
location /api/ {
    proxy_pass http://app_upstream;
    proxy_connect_timeout 5s;
    proxy_read_timeout 300s;   # 两次读取之间允许的最大空闲
    proxy_send_timeout 60s;
}

说明文档指出 proxy_read_timeout 的默认值为 60s,且定义为两次读取之间的超时,而不是整个响应的总时长。(Nginx)

  • HAProxy:常见的三个超时
defaults
  timeout connect 5s
  timeout client  60s
  timeout server  300s

server 超时过小,容易出现 504;官方运维文档建议结合访问日志分析。(HAProxy Technologies)

  • Apache httpd:反向代理的总等待
<IfModule mod_proxy.c>
  ProxyTimeout 300
</IfModule>

ProxyTimeout 用于代理请求的超时控制,文档与社区答复都给出了用法示例。(Server Fault)


把报错信息翻译成人话

报错里那句英文 The gateway did not receive a timely response from the upstream server or application 可以理解为:
当前节点并不是最终处理你请求的服务,而是一个帮你转发的中间人;它已经把请求发出去了,但上游在它的耐心耗尽之前没有回任何可用的数据,于是它只能告诉浏览器:等太久了。这正是 RFC 9110 所定义的 504 语义。(IETF Datatracker)


小结与行动建议

看到 504,就把注意力放在网关与它的上游之间的那段链路:它们是否连得上、等得起、两端是否有心跳维持,应用是否把耗时任务塞进了同步流程。把证据留在日志与指标里,用合理的超时和容量把边界框出来,再用架构手段削去长尾。这样做,504 会从频繁烦扰,变成偶发可预期的告警。

延伸阅读与权威参考
RFC 9110504 Gateway Timeout 原文定义;MDN 的状态码解释;Nginx proxy_read_timeout 官方说明;AWS ALBCloudFront 关于 504 的排障页面;Cloudflare 官方的 502/504 故障说明;HAProxy 官方运维常见问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汪子熙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值