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 "";
}
}
注意两点:
keepalive 32设置每个 worker 进程保持的最大空闲连接数- 必须设置
proxy_http_version 1.1和Connection ""——HTTP/1.0 不支持持久连接
我之前测试过一个 API 服务,没有 keepalive 时 QPS 大概 2000,加了之后直接到 4000+。翻倍不是夸张,是真的。
不过要注意: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 排查实战
原因分析
我遇到过的几种情况:
- 上游服务真的挂了:进程崩溃、端口被占用、内存溢出
- 连接数耗尽:后端服务器的连接池满了,Nginx 连不上
- 超时配置太短:像我那次凌晨报警,proxy_read_timeout 设了 60s,但后端处理要 2 分钟
- 防火墙/网络问题:安全组规则没配置,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%+。
其他优化
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;
}
}
}
总结
说了这么多,其实核心就三点:
- upstream 配置:选对负载均衡算法,启用 keepalive 连接池,配置健康检查
- 缓冲配置:搞清楚三个参数的关系,特殊场景要关闭缓冲
- 超时配置:理解三个参数各自管什么,不同场景用不同策略
凌晨三点那次事故让我学到一件事:Nginx 配置不是填填参数就完了。每个参数背后都有设计逻辑,理解了原理才能避免踩坑。
如果你刚接触 Nginx,建议先从默认配置开始,遇到问题再针对性调整——别像我当初一样随便写个 proxy_read_timeout 60s 就上生产。如果你已经踩过坑了,那这篇文章应该能帮你把零散的经验串成体系。
写完这篇文章的时候,我看了一眼自己现在的生产环境配置——keepalive 32、proxy_read_timeout 120s、least_conn 负载均衡。凌晨三点的报警再也没出现过。
常见问题
proxy_read_timeout 是总超时还是两次读取间隔?
什么时候应该关闭 proxy_buffering?
• Server-Sent Events(SSE):实时推送,缓冲会导致消息延迟
• WebSocket:双向实时通信,需要流式传输
• 大文件上传:避免内存爆掉,边收边发
keepalive 数值设多少合适?
502 和 504 的区别是什么?
负载均衡算法选哪个?
• 轮询(默认):无状态服务,公平分配
• least_conn:长连接场景(WebSocket、数据库连接池)
• ip_hash:需要会话保持(临时方案,不如用 Redis)
• hash:分布式缓存,提高命中率
upstream sent too big header 怎么解决?
sendfile + tcp_nopush + tcp_nodelay 为什么能提升性能?
13 分钟阅读 · 发布于: 2026年3月30日 · 修改于: 2026年3月30日
相关文章
shadcn/ui 与 Radix:自定义组件时如何保持无障碍
shadcn/ui 与 Radix:自定义组件时如何保持无障碍
Tailwind 性能优化:JIT、content 配置与生产体积控制
Tailwind 性能优化:JIT、content 配置与生产体积控制
Dialog、Sheet、Popover:弹层类组件的可达性与焦点管理

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