切换语言
切换主题

Nginx 反向代理完全指南:upstream、缓冲与超时

凌晨三点。手机疯狂震动——生产环境报警。

打开日志一看,全是 502 Bad Gateway。后端服务没挂,但 Nginx 配置里的超时时间设得太短,流量高峰一来,请求还没处理完就被强制中断了。我盯着那行 proxy_read_timeout 60s,心想:坑爹啊,这值我当初随便写的。

那次事故后,我花了一周时间把 Nginx 反向代理的三个核心模块彻底搞清楚:upstream 负载均衡、proxy buffer 缓冲、timeout 超时配置。说实话,这三个模块配置对了,你的反向代理能扛住十倍流量;配错了,就像我那次凌晨三点报警一样惨。

这篇文章就是把我踩过的坑、调试的经验、踩明白的原理全整理出来。如果你也在搞后端、运维,或者单纯想搞懂 Nginx 配置参数背后的逻辑,那这篇应该能帮你省不少时间。


Upstream 负载均衡:不只是”分发请求”

先聊聊基本语法

upstream 配置块是 Nginx 负载均衡的核心。最基本的写法你肯定见过:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

看起来挺简单——定义一组后端服务器,proxy_pass 指过去就行了。但说实话,光会写这个不够。真正生产环境里要考虑的东西多了:服务器挂了怎么办?有的机器配置好能不能多分点流量?长连接要不要保持?

四种负载均衡算法,各有各的场景

Nginx 默认用的是轮询(round-robin),就是按顺序挨个分发。公平,但不聪明。

如果你的后端是长连接场景——比如 WebSocket、数据库连接池——那轮询可能会让某些服务器连接数突然暴涨。这时候最少连接(least_conn)算法更合适:

upstream backend {
    least_conn;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

它会追踪每个服务器的活跃连接数,把新请求发给当前最空闲的那台。我之前有个项目用 WebSocket 推实时消息,轮询配置下某台服务器内存直接爆了——改成 least_conn 后,负载分布均匀多了。

还有一种场景你可能遇到过:用户登录后,后续请求必须打到同一台服务器上(因为 session 存在本地)。IP Hash 就是为了这个:

upstream backend {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

同一个客户端 IP 的请求会被哈希到固定的后端。不过说实话,这方案有缺陷——如果那台服务器挂了,session 就丢了。真正靠谱的还是用 Redis 存 session,把 ip_hash 当临时方案。

第四种是一致性哈希(hash),分布式缓存场景常用:

upstream backend {
    hash $request_uri consistent;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

Nginx 会创建 160 个虚拟节点 per weight unit,根据请求 URI 哈希到特定服务器。好处是缓存命中率高——同样的 URI 总是打到同一台机器。

权重配置:机器配置不均匀怎么办

后端服务器配置不一样的情况挺常见。有的机器 32G 内存、8 核 CPU,有的只有 16G、4 核。公平轮询?那性能好的机器就浪费了。

upstream backend {
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=2;
    server 192.168.1.12:8080 weight=1;
}

weight=3 的机器会收到三倍请求。配置好的多干活,配置差的少干活——这才合理。

还有个参数叫 backup,备用服务器:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080 backup;
}

backup 服务器平时不参与负载,只有前面两台都挂了才启用。有点像”替补席”——主力下场了才上场。

Keepalive 连接池:性能翻倍的秘密

这块很多人容易忽略。Nginx 默认行为是:每次请求都新建一个 TCP 连接到后端,响应完就关掉。听起来没问题?问题大了。

TCP 连接建立需要三次握手,关闭需要四次挥手。高并发场景下,这个开销很恐怖。而 keepalive 连接池能复用连接,省掉这部分开销。

配置示例:

upstream backend {
    server 192.168.1.10:8080;
    keepalive 32;  # 每个 worker 保持 32 个空闲连接
}

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

注意两点:

  1. keepalive 32 设置每个 worker 进程保持的最大空闲连接数
  2. 必须设置 proxy_http_version 1.1Connection ""——HTTP/1.0 不支持持久连接

我之前测试过一个 API 服务,没有 keepalive 时 QPS 大概 2000,加了之后直接到 4000+。翻倍不是夸张,是真的。

2倍
QPS 性能提升
来源: 实测数据:启用 keepalive 连接池后

不过要注意:keepalive 数值别设太高。我曾经在测试环境把 keepalive 设成 100,结果后端只有 1 个 ECS 容器,它直接被连接数撑爆了。生产环境的计算公式大概是:

keepalive ≈ 总QPS ÷ 单请求平均耗时 ÷ worker进程数

比如说你预估 QPS 10000,平均响应时间 50ms,有 4 个 worker:

10000 × 0.05 ÷ 4 = 125

keepalive 设 125 左右比较合理。

健康检查:挂了自动剔除

Nginx 开源版本只有被动健康检查——发现请求失败了才标记服务器不健康:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_502 http_503 http_504;
        proxy_next_upstream_tries 3;
    }
}

proxy_next_upstream 定义什么情况下重试下一台服务器:连接错误、超时、或者返回 502/503/504。proxy_next_upstream_tries 3 表示最多试 3 台。

但说实话,被动检查有延迟——必须等到请求失败才会发现服务器挂了。如果你的业务对可用性要求高,NGINX Plus 的主动健康检查更好:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

server {
    location / {
        proxy_pass http://backend;
        health_check interval=5s fails=3 passes=2;
    }
}

每 5 秒主动发健康检查请求,连续失败 3 次就标记 unhealthy,连续成功 2 次才恢复。


Proxy Buffer:缓冲是帮手还是捣乱鬼

缓冲机制到底是干嘛的

先说个概念:Nginx 从后端拿到响应后,不是直接发给客户端,而是先存到缓冲区里。

为什么?因为客户端的网络速度 unpredictable。后端可能很快吐出数据,但如果客户端网速慢,Nginx 就得”堵着”等客户端慢慢收。有了缓冲区,Nginx 可以一次性把响应存好,然后慢慢发给客户端——后端不用等,能早点处理下一个请求。

但缓冲也有代价:占用内存。响应体大、并发高的时候,内存消耗可观。

三个核心参数,搞清楚关系

proxy_buffer_size 4k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;

这三个参数我当初看得头大——名字差不多,意思又绕。后来画了个图才明白:

  • proxy_buffer_size:存响应头的缓冲区,一个请求就一个
  • proxy_buffers:存响应体的缓冲数组,格式是 数量 每个大小
  • proxy_busy_buffers_size:正在发给客户端的那部分缓冲,不能超过总 buffers 大小的一半

举个例子:proxy_buffers 8 32k,总大小是 8×32k = 256k。proxy_busy_buffers_size 64k,刚好是四分之一,符合规则。

什么时候要调这些参数?

如果你的后端响应头特别大(比如塞了很多 Cookie),可能会报 “upstream sent too big header”。解决办法:增大 proxy_buffer_size

proxy_buffer_size 16k;

如果你的响应体经常很大(比如 API 返回一大段 JSON),可以把 buffers 调大:

proxy_buffers 16 64k;

特殊场景:缓冲要关掉

有些场景缓冲反而是麻烦。

Server-Sent Events(SSE):后端持续推送事件流,如果 Nginx 缓着不发,客户端收到的就是延迟的消息。配置要关闭缓冲:

location /events {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 86400s;
}

proxy_read_timeout 86400s(一天)是因为 SSE 是长连接,不能因为超时被断掉。

WebSocket:类似,双向实时通信:

location /ws {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400s;
}

大文件上传:客户端上传 1G 文件,如果 Nginx 先全收完再转发给后端,内存直接爆。这时候要关闭请求缓冲:

location /upload {
    proxy_pass http://backend;
    proxy_request_buffering off;
    client_max_body_size 1G;
}

proxy_request_buffering off 让 Nginx 直接流式转发,边收边发。


Timeout:超时配置背后的逻辑

三个超时参数,各管各的事

proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;

名字看着差不多?其实分工明确:

  • proxy_connect_timeout:Nginx 等待建立 TCP 连接的时间。如果后端服务器响应慢(比如网络拥堵、防火墙拦截),超过这个时间就放弃。
  • proxy_read_timeout:连接建立后,Nginx 等待后端返回数据的时间。两次读取操作之间的间隔超过这个值就算超时。
  • proxy_send_timeout:Nginx 发送请求体给后端的时间限制。

这里有个容易混淆的点:proxy_read_timeout 不是总超时,而是两次读取操作之间的间隔。如果你的后端要处理 5 分钟才能返回,但处理期间会不断发点数据(比如心跳包),那 proxy_read_timeout 60s 就够了。但如果后端完全沉默 5 分钟,那得设 proxy_read_timeout 300s

超时和 502/504 的关系

凌晨三点那次报警,我学到的一个重要教训:

  • 502 Bad Gateway:Nginx 根本没连上后端——可能后端挂了、端口不通、防火墙拦截
  • 504 Gateway Timeout:Nginx 连上了,但后端处理太久没返回数据

举个例子:proxy_connect_timeout 10s,如果后端 15 秒才响应连接请求,Nginx 会返回 502。但如果连接很快建立,后端处理了 2 分钟才吐数据,而 proxy_read_timeout 60s,那就会返回 504。

不同场景的超时策略

API 服务:一般 30-60 秒够了。API 响应应该快,超时设短能快速发现慢请求:

proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;

文件处理:导出报表、生成 PDF 这类操作可能要几分钟。超时要放宽:

proxy_connect_timeout 10s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;

流式服务:视频直播、WebSocket、SSE——这些是长连接,设成一天也正常:

proxy_read_timeout 86400s;

502/504 排查实战

原因分析

我遇到过的几种情况:

  1. 上游服务真的挂了:进程崩溃、端口被占用、内存溢出
  2. 连接数耗尽:后端服务器的连接池满了,Nginx 连不上
  3. 超时配置太短:像我那次凌晨报警,proxy_read_timeout 设了 60s,但后端处理要 2 分钟
  4. 防火墙/网络问题:安全组规则没配置,iptables 拦截了请求

日志诊断方法

第一步永远是看 error_log:

error_log /var/log/nginx/error.log warn;

常见的错误信息:

upstream timed out (110: Connection timed out) while reading response header from upstream

这是 504,读取超时。

connect() failed (111: Connection refused) while connecting to upstream

这是 502,连接被拒绝——后端没在监听。

更高级的玩法是自定义日志格式,打印 upstream 状态:

log_format upstream_status '$status $upstream_status $upstream_response_time';

access_log /var/log/nginx/access.log upstream_status;

你会看到类似 200 200, 200, 502 0.5, 1.2, 3.0 这样的输出——清楚显示哪台后端返回了什么状态码、耗时多少。

典型解决方案

场景一:后端处理慢,频繁 504

解决:增大 proxy_read_timeout,同时确认后端真的能处理完。别光调 Nginx,后端超时也要同步调整。

场景二:连接被拒绝,502

解决:检查后端进程是否运行、端口是否监听、防火墙规则是否允许。

netstat -tlnp | grep 8080
ps aux | grep your_app

场景三:高并发下连接数耗尽

解决:增大后端连接池限制,或者启用 Nginx upstream keepalive 减少连接创建开销。


性能优化最佳实践

Worker 配置

Nginx 是多进程模型。worker_processes 设置进程数,一般等于 CPU 核心数:

worker_processes auto;

auto 会自动检测 CPU 核心数。如果你的机器是 8 核,就会有 8 个 worker 进程。

worker_connections 是每个 worker 能处理的最大连接数:

events {
    worker_connections 4096;
}

理论最大并发连接数 = worker_processes × worker_connections。8 核 × 4096 = 32768。不过实际值会受系统文件描述符限制影响。

TCP 优化三板斧

sendfile on;
tcp_nopush on;
tcp_nodelay on;

这三个参数组合使用能显著提升性能:

  • sendfile on:启用内核级文件传输,绕过用户态缓冲区
  • tcp_nopush on:配合 sendfile,批量发送数据包而不是一个一个发
  • tcp_nodelay on:小数据包立即发送,不等待缓冲区填满

我测过静态文件服务,这三个开关加上后,吞吐量提升 30%+。

30%+
吞吐量提升
来源: 实测数据:启用 sendfile + tcp_nopush + tcp_nodelay

其他优化

gzip 压缩:文本响应压缩后传输,节省带宽:

gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1024;

文件描述符限制:高并发下可能不够用。检查系统限制:

ulimit -n

如果只有 1024,得调高。编辑 /etc/security/limits.conf

* soft nofile 65535
* hard nofile 65535

完整配置示例

生产环境的推荐配置模板:

# 基础配置
worker_processes auto;

events {
    worker_connections 4096;
    multi_accept on;
}

http {
    # TCP 优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # Keepalive
    keepalive_timeout 30;
    keepalive_requests 100;

    # 缓冲配置
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 32k;
    proxy_busy_buffers_size 64k;

    # 超时配置
    proxy_connect_timeout 10s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;

    # gzip
    gzip on;
    gzip_types text/plain text/css application/json;

    upstream backend {
        least_conn;
        server 192.168.1.10:8080 weight=3;
        server 192.168.1.11:8080 weight=2;
        server 192.168.1.12:8080 backup;
        keepalive 32;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;

            proxy_next_upstream error timeout http_502 http_503 http_504;
            proxy_next_upstream_tries 3;
        }

        # SSE 专用配置
        location /events {
            proxy_pass http://backend;
            proxy_buffering off;
            proxy_read_timeout 86400s;
        }
    }
}

总结

说了这么多,其实核心就三点:

  1. upstream 配置:选对负载均衡算法,启用 keepalive 连接池,配置健康检查
  2. 缓冲配置:搞清楚三个参数的关系,特殊场景要关闭缓冲
  3. 超时配置:理解三个参数各自管什么,不同场景用不同策略

凌晨三点那次事故让我学到一件事:Nginx 配置不是填填参数就完了。每个参数背后都有设计逻辑,理解了原理才能避免踩坑。

如果你刚接触 Nginx,建议先从默认配置开始,遇到问题再针对性调整——别像我当初一样随便写个 proxy_read_timeout 60s 就上生产。如果你已经踩过坑了,那这篇文章应该能帮你把零散的经验串成体系。

写完这篇文章的时候,我看了一眼自己现在的生产环境配置——keepalive 32、proxy_read_timeout 120s、least_conn 负载均衡。凌晨三点的报警再也没出现过。


常见问题

proxy_read_timeout 是总超时还是两次读取间隔?
两次读取操作之间的间隔。如果后端在处理期间持续发送数据(比如心跳包),即使总处理时间 5 分钟,proxy_read_timeout 60s 也够用。但如果后端完全沉默 5 分钟,必须设成 300s。
什么时候应该关闭 proxy_buffering?
三类场景必须关闭:

• Server-Sent Events(SSE):实时推送,缓冲会导致消息延迟
• WebSocket:双向实时通信,需要流式传输
• 大文件上传:避免内存爆掉,边收边发
keepalive 数值设多少合适?
计算公式:keepalive ≈ 总QPS × 单请求平均耗时 ÷ worker进程数。比如 QPS 10000、响应时间 50ms、4 个 worker,keepalive 设 125 左右。别设太高——我曾经设成 100 把后端撑爆了。
502 和 504 的区别是什么?
502 Bad Gateway 表示 Nginx 连不上后端(服务挂了、端口不通、防火墙拦截)。504 Gateway Timeout 表示连上了但响应超时(后端处理慢)。诊断方法完全不同:502 检查进程和端口,504 检查超时配置和后端处理时间。
负载均衡算法选哪个?
按场景选:

• 轮询(默认):无状态服务,公平分配
• least_conn:长连接场景(WebSocket、数据库连接池)
• ip_hash:需要会话保持(临时方案,不如用 Redis)
• hash:分布式缓存,提高命中率
upstream sent too big header 怎么解决?
增大 proxy_buffer_size。后端响应头太大(比如塞了很多 Cookie)会超过默认的 4k 缓冲区。改成 proxy_buffer_size 16k 通常能解决。
sendfile + tcp_nopush + tcp_nodelay 为什么能提升性能?
sendfile 绕过用户态直接内核传输,tcp_nopush 批量发送减少包数,tcp_nodelay 小数据即时发送不等待。三者组合,静态文件吞吐量实测提升 30%+。

13 分钟阅读 · 发布于: 2026年3月30日 · 修改于: 2026年3月30日

评论

使用 GitHub 账号登录后即可评论

相关文章