4. 代理与网关:反向代理、正向代理、Nginx、HAProxy 配置不当导致的错误

说到「Remote end closed connection without response」这个错误,代理和网关绝对是重灾区。我这些年排查过的案例里,至少有一半跟它们有关。你想想看,代理层就像个中间人,它要是脾气不好或者理解错了,直接把后端连接掐了,前端收到的就是这句冷冰冰的提示。

4.1 反向代理 vs 正向代理:谁更容易出问题?

先理清概念。反向代理是替后端服务器挡在前面的,客户端不知道后端是谁。正向代理是替客户端出去访问的,服务器不知道客户端是谁。嗯,这两种场景下,连接被关闭的原因其实不太一样。

代理类型 典型场景 常见错误原因
反向代理 Nginx 代理后端 API 后端超时、缓冲区溢出、upstream 配置错误
正向代理 公司内网通过 Squid 访问外网 连接池耗尽、SSL 握手失败、ACL 限制

我个人习惯把反向代理的问题分成两类:一类是代理主动关闭连接,另一类是代理被动收到了后端的关闭信号然后转发给客户端。前者往往是配置问题,后者往往是后端服务本身扛不住了。

4.2 Nginx 配置不当:最常见的坑

Nginx 是我用得最多的反向代理,也是踩坑最多的。说白了,Nginx 默认配置是给静态网站用的,拿来代理 API 服务,不改几个参数肯定要出问题。

4.2.1 proxy_read_timeout 太短

这个参数控制 Nginx 等待后端响应的最长时间。默认是 60 秒。如果你的后端接口需要处理大量数据,或者有长轮询逻辑,60 秒根本不够用。

我曾经遇到过一个报表导出功能,用户点击导出后,后端要生成 Excel 文件,耗时大概 90 秒。Nginx 在 60 秒时直接断开了连接,前端收到 "Remote end closed connection without response"。排查了半天才发现是 proxy_read_timeout 的问题。

解决方案很简单,根据业务场景调整:

# 针对 API 接口,建议设置 120 秒以上
location /api/ {
    proxy_pass http://backend;
    proxy_read_timeout 120s;
    proxy_connect_timeout 30s;
    proxy_send_timeout 30s;
}

# 针对流式接口或长轮询,可以设置更大
location /stream/ {
    proxy_pass http://stream_backend;
    proxy_read_timeout 300s;
    proxy_buffering off;  # 流式接口建议关掉缓冲
}

4.2.2 proxy_buffer 配置不当

Nginx 默认会缓冲后端响应,等攒够了再发给客户端。如果后端返回的数据量很大,而缓冲区太小,Nginx 可能会写磁盘,或者直接报错断开连接。

我记得有一次,后端返回了一个 10MB 的 JSON 数据,Nginx 默认的 proxy_buffer_size 只有 4KB,proxy_buffers 也只有 8 个 4KB 的块。结果 Nginx 一边缓冲一边往磁盘写,最后超时了,连接被关闭。

# 增大缓冲区,避免磁盘 I/O
location /api/ {
    proxy_buffering on;
    proxy_buffer_size 8k;
    proxy_buffers 16 8k;
    proxy_busy_buffers_size 16k;
    proxy_temp_path /tmp/nginx_proxy_temp 1 2;
    proxy_max_temp_file_size 1024m;
}
小技巧:如果后端接口返回的数据不需要在 Nginx 层做任何处理,直接关掉缓冲反而更高效。设置 proxy_buffering off; 后,Nginx 会像水管一样直接透传数据,减少内存占用和延迟。

4.2.3 upstream 配置错误

upstream 块里的配置也经常出问题。比如 max_fails 和 fail_timeout 设置得太激进,导致健康的后端被误判为不可用。

upstream backend {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    
    # 如果后端重启频繁,可以调大 fail_timeout
    # server 192.168.1.12:8080 max_fails=5 fail_timeout=60s;
    
    keepalive 32;  # 启用长连接,减少握手开销
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

这里有个细节:如果 upstream 里启用了 keepalive,但后端不支持 HTTP/1.1 长连接,Nginx 可能会收到后端的 RST 包,然后断开连接。嗯,这个坑我踩过两次才记住。

4.3 HAProxy 配置不当:容易被忽略的细节

HAProxy 比 Nginx 更专注于四层和七层负载均衡,但配置不当同样会导致连接被关闭。

4.3.1 timeout 参数设置

HAProxy 的 timeout 参数非常多,而且每个都有不同的作用域。我见过最典型的问题是 timeout server 设置得太小,导致后端还在处理请求,HAProxy 就把连接关了。

# HAProxy 配置示例
defaults
    mode http
    timeout connect 5s
    timeout client 30s
    timeout server 30s    # 这个值要跟后端接口耗时匹配
    timeout queue 10s
    timeout http-request 10s
    timeout http-keep-alive 60s

backend api_servers
    balance roundrobin
    server api1 192.168.1.10:8080 check inter 3s fall 3 rise 2
    server api2 192.168.1.11:8080 check inter 3s fall 3 rise 2
核心原则:timeout server 的值必须大于后端接口的最大处理时间。如果后端接口平均耗时 20 秒,偶尔会到 50 秒,那 timeout server 至少设到 60 秒。别卡得太死,留点余量。

4.3.2 连接队列溢出

HAProxy 默认的连接队列大小是 1024。如果并发请求量很大,队列满了之后,新的连接会被直接拒绝,客户端就会收到连接被关闭的错误。

# 增大连接队列
global
    maxconn 10000
    tune.maxaccept 100

defaults
    maxconn 5000
    backlog 4096  # 增大 backlog,应对突发流量

我个人习惯在生产环境把 maxconn 设到硬件能承受的上限,同时配合监控告警。一旦连接数接近上限,就要考虑扩容了。

4.4 代理层连接关闭的排查思路

遇到 "Remote end closed connection without response" 时,如果怀疑是代理层的问题,我一般按这个顺序排查:

  1. 看代理日志:Nginx 的 error.log、HAProxy 的日志,都会记录连接关闭的原因。比如 Nginx 会写 "upstream prematurely closed connection"。
  2. 检查超时配置:把所有 timeout 参数列出来,跟后端接口的实际耗时对比。
  3. 检查缓冲区配置:特别是 Nginx 的 proxy_buffers,看是不是太小导致写磁盘了。
  4. 检查后端健康检查:有时候代理层把后端标记为 down 了,但实际后端是正常的。调整 max_fails 和 check 参数。
  5. 抓包确认:在代理服务器上用 tcpdump 抓包,看是谁先发的 FIN 或 RST。这个最直接,谁先动手谁有问题。
避坑指南:我曾经在排查一个线上问题时,Nginx 日志里啥都没写,但客户端就是报连接被关闭。后来抓包发现,是后端服务器在响应完成后主动发了 RST 包,Nginx 只是如实转发给了客户端。所以别只盯着代理层,后端也可能有问题。

4.5 知识体系:代理与网关错误的核心逻辑

下面这张图是我自己总结的,代理层连接关闭的几种典型路径。你可以对照着排查问题。

代理层连接关闭的典型路径 客户端 代理层 (Nginx/HAProxy) 后端服务 路径 1:代理主动关闭连接 原因:proxy_read_timeout 超时、缓冲区溢出、upstream 误判后端不可用 路径 2:后端主动关闭连接 原因:后端处理超时、服务崩溃、连接池耗尽、主动发送 RST 包 路径 3:网络层面中断 原因:防火墙拦截、TCP 重置、网络抖动导致连接中断 排查建议:先看代理日志 → 检查超时配置 → 抓包确认谁先发 FIN/RST

这张图把代理层连接关闭的三种路径都画出来了。实际排查时,先确定是哪种路径,再针对性地检查配置。路径 1 和路径 2 的解决方案完全不同,别搞混了。

好了,代理和网关这块的内容就这些。记住一句话:代理层配置的核心是「匹配」,匹配后端的能力、匹配业务的耗时、匹配网络的状况。配错了,连接就会被关闭。