Nginx 反代为什么总在 60 秒断开:三层超时配置的排查顺序
反向代理后面挂长任务,最常见的现象是「刚好 60 秒断一次」。有人改 proxy_read_timeout 就好了,有人改完照样断,原因在于超时不止一层,而你改的那层未必是实际生效的那层。
先说结论:一次请求从客户端到上游,至少要穿过三层超时——客户端与 Nginx 之间、Nginx 与上游之间、上游应用自己。三层的默认值和默认行为都不一样,必须从外往里查。
第一层:Nginx 与上游之间
这是最常被提到的一层,对应三个指令:
- proxy_connect_timeout:建立 TCP 连接的超时,默认 60s。
- proxy_send_timeout:两次写操作之间的间隔,不是整个请求的总时长。
- proxy_read_timeout:两次读操作之间的间隔,同样不是总时长。
这里最容易误读的是后两个:它们是「空闲间隔」,不是「总耗时」。一个跑 10 分钟但每 30 秒吐一次数据的流式接口,proxy_read_timeout 设 60s 完全够用;反过来,一个卡住不动 61 秒的请求,即使总时长没超也会断。所以改这层之前先确认:你的请求是「一直没数据」还是「总时长太长」。
第二层:客户端与 Nginx 之间
如果 Nginx 到上游是通的,但客户端照样在 60 秒报错,看这两个:
- keepalive_timeout:连接空闲多久关闭,默认 65s。注意它管的是空闲,不是请求进行中。
- client_body_timeout / client_header_timeout:默认 60s。
真正的元凶往往是客户端或中间设备:浏览器的 fetch 一般没有默认超时,但 axios 默认没有、某些 SDK 默认 60s;公司网关、WAF、SLB 也常带 60s 的空闲切断。这时候你在 Nginx 里怎么调都没用,因为刀在别人手里。判据是看 Nginx 的 access log 里请求有没有被记成 499(客户端主动断开)——如果是 499,说明是客户端先走的。
第三层:上游应用自己
应用侧的超时藏在框架里:Gunicorn 的 --timeout 默认 30s 且是「worker 无响应就杀」;Tomcat 的 connectionTimeout 默认 20s;Spring 的 RestTemplate 不设超时会一直等。这层的症状是 Nginx 日志里出现 502 而不是 504——因为上游直接把连接关了。
排查顺序
1. 先看 Nginx error.log 里是 upstream timed out(504)还是 connection refused/reset(502)。
2. 504 且上游日志显示任务还在跑:改 proxy_read_timeout,并且确认上游有没有心跳。
3. 504 但上游日志根本没收到请求:问题在第一层的 proxy_connect_timeout,或者上游地址/端口不对。
4. access log 里是 499:问题在客户端或中间网关,去查那条链路。
5. 502:去看应用自己的 worker 超时。
两个实践细节
第一,别只调超时。长任务更该改成「提交任务拿 task_id + 轮询/回调」,把长连接变成短请求,超时问题自然消失。调大超时只是把问题推后。
第二,超时值要分层递减。客户端 120s > 网关 90s > Nginx 75s > 应用 60s,这样永远是内层先超时并留下日志,外层拿到的是明确的错误码,而不是自己也超时导致「谁都没记日志」。反过来配(内层比外层大)就会出现外层先断、内层还在跑、日志里一片空白的经典困局。
小结
「60 秒断开」是个现象,不是原因。先分清是空闲超时还是总时长超时,再从 access log 的 499/504/502 三个码定位到具体哪一层,最后才是改配置。层级顺序配反了,改哪一层都像没改。
- 点赞
- 收藏
- 关注作者
评论(0)