recv() failed (104: Connection reset by peer) 表明后端假死而非宕机,需抓包确认RST来源(upstream/client/中间设备),结合$upstream_response_time与$request_time定位假死窗口,并检查JVM GC、Go goroutine泄漏或Node.js事件循环阻塞等根本原因。

看到大量 recv() failed (104: Connection reset by peer),说明不是偶发网络抖动,而是后端服务在响应过程中被强制中断——进程没挂,但已无法正常回包。关键不是“连不上”,而是“连上了却突然被掐断”。
先确认 RST 来自哪一端
仅看 Nginx 日志无法定责,必须抓包验证 RST 源头:
- 在 Nginx 服务器上执行:
tcpdump -i any 'host <upstream_ip> and port <upstream_port>' -w rst.pcap - 复现问题后,用 Wireshark 打开 pcap,过滤
tcp.flags.reset == 1 - 检查 RST 包的 源 IP:若是 upstream 地址,问题在后端;若是负载均衡器或防火墙 IP,需查中间设备策略;若源是客户端,要排查前端行为或出口网关
- 特别注意日志中是否含
while reading response header from upstream(RST 来自 upstream)或while sending to client(RST 来自 client)
结合 access_log 锁定假死时间窗口
Nginx 的 $upstream_response_time 和 $request_time 是判断后端是否“卡住”的核心依据:
- 如果
upstream_response_time为-,说明后端根本没发出任何响应字节,大概率进程僵死或崩溃前已断连 - 如果
upstream_response_time有值(如3.210),但request_time显著更大(如15.678),说明 Nginx 等待超时后主动关闭连接,而那个数值就是后端实际存活并尝试响应的时长 - 同一秒内多个错误的
upstream_response_time高度集中(如都落在3000ms±5ms),基本可判定后端设置了统一硬超时(如 Spring Boot 的server.connection-timeout=3s或 Tomcat 的connectionTimeout)
检查后端服务是否处于假死状态
“假死”指进程仍在运行,但因资源耗尽或逻辑阻塞失去响应能力:
-
JVM 应用:检查 GC 日志,重点关注 Full GC 耗时是否超过 Nginx 的
proxy_read_timeout;用jstack <pid>看线程是否全部 BLOCKED 或 WAITING -
Go 应用:观察 goroutine 数量是否持续上涨(
runtime.NumGoroutine()),内存是否接近 limit 触发 panic 后 socket 被回收 -
Node.js:检查是否有 CPU 密集型同步操作(如大数组排序、灾难性正则)阻塞事件循环;用
process.hrtime()或 Clinic.js 定位耗时函数 - 通用线索:后端无 crash 日志、CPU 使用率可能不高、但请求堆积、线程/协程数异常飙升、健康检查返回 200 却无法处理真实请求
验证与绕过 Nginx 直连后端
排除 Nginx 层干扰,快速验证是否真为后端问题:
- 用
curl -v http://<upstream_ip>:<upstream_port>/health或业务接口,观察是否同样超时或 RST - 若直连也复现,问题 100% 在后端;若直连正常,则回头检查 Nginx 配置(如
proxy_buffer_size是否小于后端响应头大小、proxy_read_timeout是否过短) - 临时关闭 Nginx 的 keepalive 到 upstream(注释掉
proxy_http_version 1.1和proxy_set_header Connection ""),看错误是否减少——若明显缓解,说明长连接复用放大了后端连接管理缺陷


















