
接手一个需要支撑高并发的服务后端挂了、流量分不匀第一个想到的工具基本就是 Nginx。它的反向代理和负载均衡能力是绝大多数 Web 架构里的基础配置也是我从接触 Linux 运维到现在用得最频繁、最顺手的能力之一。今天把一套完整的配置示例拆开讲假设有三台后端服务器写清楚怎么配、为什么这么配、上线之后又该怎么验证和排错。这篇东西适合刚接触 Nginx 的同学对照着抄作业也适合已经会配但对参数含义一知半解的人补一补“为什么”。先说清楚这套配置解决的是什么问题。你有一个域名或者说一个入口地址请求进来之后Nginx 负责把请求转发给后端真正的业务服务。如果后端只有一台机器坏掉就是全体不可用如果有很多台就需要按策略分流量。反向代理解决的是“入口统一、后端隐藏”的问题负载均衡解决的是“流量怎么分、后端怎么撑”的问题。Nginx 一台机器同时干这两件事配置文件写明白后面运维省心一大截。1. 先从原理说起反向代理与负载均衡到底在解决什么问题1.1 “反向”在哪里方向不同用途完全不同很多初学者容易把正向代理和反向代理搞混。正向代理是客户端主动配置代理代理替客户端去请求外部资源典型场景是局域网内访问外网。反向代理则是客户端根本不知道后端服务器的存在它只访问 NginxNginx 再把请求转给内部后端。方向一反过来安全性、扩展性、可维护性就完全不一样。为什么必须要 Nginx 挡在前面直接暴露后端服务器不是更简单吗你想想几个场景后端服务要升级重启如果客户端直连升级的瞬间连接全部断掉后端有多台机器客户端到底连哪一台谁来决定日志、限流、鉴权这些通用能力如果每个后端各自实现一遍重复劳动且容易漏。Nginx 把这些公共问题收敛到一个入口后端只管业务逻辑这是架构上比较经典的分层思路。我用生活化的类比后端服务器像厨房里的几个灶台Nginx 像餐厅的服务员。客人不会自己冲进厨房找锅他只跟服务员下单服务员根据哪个灶台空闲、哪个灶台做得快把单子分下去。客人体验到的只有一个入口厨房内部怎么安排是后厨的事。这就是反向代理加负载均衡的直观画面。1.2 负载均衡的基础算法轮询、权重、IP_HASH怎么选Nginx 默认使用轮询round-robin算法请求按顺序轮流分发到每台后端。这是最省事、最公平的策略适合后端配置差不多、接口响应时长也差不多的场景。但真实环境里后端资源往往不齐整比如两台是 8 核 16G一台是 4 核 8G平均分显然浪费了高配机器。这时候要用 weight权重参数让高配机器多扛一些流量。另一种常见策略是 ip_hash根据客户端 IP 的哈希值把请求固定分发到某一台后端。这个策略的核心价值是“会话保持”比如用户登录状态存在后端内存里如果第二次请求被转发到另一台机器登录态就丢了。虽然现在有 Redis 这种集中式会话存储但一些老系统、小项目还是会依赖 ip_hash 解决粘滞问题。还有 least_conn最少连接数Nginx 会把请求分给当前并发连接数最少的那台后端。这个策略对长连接、耗时差异大的接口比较友好但计算开销稍高适合后端服务处理能力差异大、接口耗时不平均的业务。我见过不少团队默认用轮询结果某台后端偶尔出现 CPU 飙高排查半天发现是接口响应差异大导致的倾斜换成 least_conn 之后明显改善。1.3 为什么单独强调超时和重试机制后端服务器不是永动机总有假死、报错、响应迟缓的时候。Nginx 负载均衡不只是“分发”还承担“故障转移”的职责。默认配置下如果某台后端连接失败Nginx 会把请求转给下一台但如果是响应超时转发逻辑会更复杂。这里涉及的参数是 proxy_connect_timeout、proxy_read_timeout它们决定了 Nginx 愿意等后端多久。很多人忽略的是Nginx 默认只在“连接失败”时重试不会在所有超时场景下自动重试避免把重复请求都打给后端。这点要结合具体业务去调读接口可以适当放宽重试写接口要谨慎否则可能出现重复下单或者重复扣款之类的严重问题。配置里我一般把 proxy_next_upstream 参数显式列出来而不是依赖默认行为这样出问题的时候一眼能看明白。2. 完整配置示例三台后端机的标准写法2.1 upstream 块定义后端服务器池先把核心配置亮出来这是一个典型的、可直接使用的反向代理加负载均衡配置。配置文件放在 /etc/nginx/conf.d/ 下比如命名为 loadbalance.confupstream backend_servers { # 默认轮询方式 server 192.168.1.11:8080 weight1 max_fails2 fail_timeout30s; server 192.168.1.12:8080 weight2 max_fails2 fail_timeout30s; server 192.168.1.13:8080 backup; }这个 upstream 块是负载均衡的核心。三行 server 指令分别定义了三台后端权重分别为 1 和 2也就是说 11 号机承担约三分之一流量12 号机承担约三分之二流量。13 号机标注为 backup平时不参与分发只有前面两台都挂了或者不可用的时候Nginx 才会把请求转给它。这种主备模式在生产环境非常实用避免备用机白白空转到过热也便于紧急切换。max_fails 和 fail_timeout 配合使用表示在 30 秒内如果这台服务器失败达到 2 次Nginx 会把这台标记为不可用暂时不转发请求给它是 timeout 窗口过后再重新尝试。这个参数就是故障转移的“闸门”决定了后端失败后多久能被摘除、多久能被恢复探测。2.2 server 块入口监听与请求转发继续看完整的 server 块配置server { listen 80; server_name www.example.com; access_log /var/log/nginx/example_access.log; error_log /var/log/nginx/example_error.log; location / { proxy_pass http://backend_servers; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }listen 80 表示监听标准的 HTTP 端口server_name 用来匹配请求头里的 Host这是 Nginx 多站点共存的基石。请求进来之后进入 location /所有路径都会匹配到这个块然后 proxy_pass 把请求公开给 upstream 里定义的 backend_servers 这个后端池。proxy_set_header 四行是很多新手容易漏的。默认情况下 Nginx 转发请求时后端看到的 IP 其实是 Nginx 的 IP客户端真实 IP 丢失了。X-Real-IP 和 X-Forwarded-For 手动把真实 IP 带过去后端的日志、埋点、风控才能正常工作。我接手过不止一个项目后端拿不到客户端 IP排查了老半天最后发现是 Nginx 模板里少了一行 proxy_set_header这种基础问题尽早写进模板一步到位。2.3 location 细化不同业务路径转不同后端生产环境里很少只有一个后端池。常见的场景是 /api/ 开头的请求走业务接口后端/static/ 开头直接本地文件或走静态资源服务其他路径走页面渲染服务。这时候就需要在 location 层做分流upstream api_servers { server 192.168.1.21:8000; server 192.168.1.22:8000; } upstream web_servers { server 192.168.1.31:80; server 192.168.1.32:80; } server { listen 80; server_name www.example.com; location /api/ { proxy_pass http://api_servers; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /static/ { alias /var/www/static/; expires 7d; } location / { proxy_pass http://web_servers; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有个容易踩坑的细节location /api/ 和 location / 的匹配优先级Nginx 默认是前缀匹配时最长路径优先所以 /api/ 的请求会精确地进到 /api/ 块不会掉到 / 块。理解了这点你就知道为什么 location 顺序有时候不重要有时候又影响很大关键看路径的覆盖关系。2.4 proxy_pass 结尾带不带斜杠的差异这个坑我必须单独拎出来说。proxy_pass 后面的 URL 带不带路径 /转发行为完全不同。如果写的是 proxy_pass http://backend_servers;不带路径Nginx 会把完整的原始 URI 原样转发给后端如果写的是 proxy_pass http://backend_servers/;带一个斜杠Nginx 会把 location 匹配的那段前缀替换掉再转发。我来举个例子。请求访问的是 /api/user/infolocation 是 /api/。不带斜杠时后端拿到的 URI 是 /api/user/info带斜杠时后端拿到的 URI 变成 /user/info。这两种行为决定了后端接口定义的路径风格。很多前后端分离项目里前端调 /api/user/info后端实际暴露 /user/info靠的就是这层替换。如果配反了要么 404要么路由全部错乱排查起来很让人头疼。3. 实操过程从零到能跑通全套流程3.1 环境准备与 Nginx 安装先说明我的实验环境两台 Ubuntu 22.04 虚拟机加一台本机Nginx 版本是 1.24.0后端用最简单的 Python HTTP 服务模拟。你可以用任何后端替换只要端口对得上就行。安装 Nginx 我建议直接用发行版官方源不求最新但求稳定。在 Ubuntu/Debian 上就是两条命令sudo apt update sudo apt install -y nginxCentOS/RHEL 系则用sudo yum install -y nginx安装完先不急着配先确认服务能起来。systemctl start nginx 之后浏览器访问本机 IP 能看到 Nginx 欢迎页说明基础环境没问题。这一步很重要避免后面配置出问题时分不清是 Nginx 的问题还是配置文件的问题。3.2 配置整段写入与语法检查我不会去修改 /etc/nginx/nginx.conf 主文件而是新建一个独立配置文件这样后续维护、删除、对比都方便。在 /etc/nginx/conf.d/ 下创建 loadbalance.conf把前面那套 upstream 和 server 配置写进去。这里有个前提主配置里 http 块内通常已经有 include /etc/nginx/conf.d/*.conf; 这一行Ubuntu 默认自带其余系统需要确认一下。写完之后最关键的一步是语法检查。Nginx 提供了现成的工具sudo nginx -t这个命令会检查所有配置文件语法同时检查引用的文件路径是否合法。输出类似nginx: configuration file /etc/nginx/nginx.conf test is successful看到 successful 才算通过。我见过太多同学跳过这步直接 restart结果配置有误服务直接挂掉。nginx -t 这个习惯一旦养成能帮你省下无数个凌晨的排障时间。3.3 重载配置与验证分发效果语法没问题之后接下来就是让配置生效。这里我推荐用 reload 而不是 restart。reload 会平滑加载新配置正在进行的请求不会断掉restart 是直接重启进程会造成瞬间的连接中断。生产环境里能用 reload 就不用 restart这是运维的基本礼仪。sudo systemctl reload nginx重载之后怎么确认配置真的生效首先是看进程状态sudo systemctl status nginx然后验证实际的转发行为。我习惯先在每台后端起一个能显示自身标识的 HTTP 服务最简单的是写一个小脚本from http.server import HTTPServer, BaseHTTPRequestHandler class Handler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header(Content-Type, text/plain) self.end_headers() self.wfile.write(bThis is backend 11) HTTPServer((0.0.0.0, 8080), Handler).serve_forever()三台后端分别改成 11、12、13起在不同的端口上。然后在客户端侧持续请求 Nginxfor i in $(seq 1 10); do curl -s http://127.0.0.1/ ; echo; done如果权重配置是 1:2:0你会看到大约三分之一的请求返回 backend 11三分之二返回 backend 1213 号全程不出现。连续执行多轮比例基本稳定在 1:2 附近说明权重在真实生效。如果发现请求一直落在同一台先去检查是否有缓存、是否有 keepalive 连接粘连、location 是否匹配到了别的代理规则。3.4 顺手验证一下 backup 的故障切换权重验证通过后我还会顺手做一次故障模拟。把 11 号后端停掉也就是把那个 Python 进程 kill 掉再连续请求 Nginx。这时候由于 max_fails 和 fail_timeout 的生效Nginx 会在最多几次失败后把 11 号摘除请求全部打到 12 号。然后再把 11 号重启等 fail_timeout 窗口过了Nginx 又会探测到它恢复流量自动重新分配。这个验证非常值钱模拟的是真实的后端宕机场景确认故障转移真能跑得通心里才有底。4. 进阶参数与生产环境调优4.1 连接复用与 keepalive 配置默认情况下Nginx 和后端之间每次请求都会新建一条 TCP 连接请求结束就断开。高并发场景下这条连接的建立开销会非常可观。upstream 块里可以启用长连接upstream backend_servers { server 192.168.1.11:8080; server 192.168.1.12:8080; keepalive 32; }keepalive 32 表示 Nginx 为每个 worker 进程保留最多 32 条空闲的长连接复用给后续请求。配套地location 里还要加两行location / { proxy_http_version 1.1; proxy_set_header Connection ; }为什么必须加上因为后端服务判断是否长连接依赖 HTTP 协议版本和 Connection 头。HTTP/1.0 默认是短连接必须显式改成 1.1 并清空 Connection 头否则 keepalive 根本不生效。我见过一些配置抄了 upstream 里的 keepalive 却忘了改 location结果连接池一直是空的性能没提上来。这两处必须成对出现缺一不可。4.2 权重与故障转移参数再深入再展开说说 weight、max_fails、fail_timeout 这三个参数在实际项目里的调整经验。weight 不只是简单的比例关系它影响的是 Nginx 的调度权重。我通常在刚上线新机器时把权重调低比如 0.5观察一段时间确认新机器稳定再一点点往上加。这比一次性把流量切过去稳得多。如果新机器代码有问题或配置不对权重低只是影响少量用户可以快速回退。max_fails 和 fail_timeout 我一般搭配成 max_fails2 fail_timeout30s这组参数意思是 30 秒内失败 2 次就摘除 30 秒。如果后端服务启动很慢比如 Java 应用冷启动要四十多秒那 fail_timeout 就得相应调大否则健康检查频繁把正在重启的节点摘除反而引发雪崩。这种“参数跟着业务特性和后端启动速度走”的思维比死记硬背一组数值重要得多。4.3 会话保持的两种主流方案前面提到 ip_hash 可以实现会话保持但它有一个坑如果某个客户端 IP 后面是大量用户比如公司出口 NAT那这堆用户的请求会全部落到同一台后端造成热点。这种场景下更好用的是 cookie 粘滞stickyNginx Plus 商业版支持开源版可以用第三方模块或者在后端会话层解决。我的建议是不要过分依赖 Nginx 层的会话保持优先把会话状态抽到 Redis 或数据库里让后端变成无状态服务。无状态的好处是任何请求落在哪台后端都一样扩容缩容随时做后端重启不影响用户。如果历史包袱太重不得不依赖内存 Session再用 ip_hash 顶一阵但记得留时间改造。4.4 常见超时参数一表看懂超时参数是生产环境排障的常客我把最常用的几个整理成一个速查表参数名默认值作用说明实际建议proxy_connect_timeout60sNginx 与后端建立 TCP 连接的超时内网建议 5-10s避免后端假死拖垮连接proxy_read_timeout60sNginx 等待后端响应的超时按接口最慢耗时放宽一般 30-120sproxy_send_timeout60sNginx 把请求体发给后端的超时涉及大文件上传时酌情调大proxy_next_upstreamerror timeout触发重试的错误类型写接口建议去掉 timeout只保留 error最后一个参数很容易引起数据重复问题我再强调一遍。如果后端口误删除了记录但返回 200Nginx 不会重试如果后端处理到一半超时客户端可能已经收到错误或被重试这会导致重复写入。所以写接口的代理重试策略要保守读接口可以激进一些。我的配置里写接口一般这样限制location /api/write/ { proxy_pass http://backend_servers; proxy_next_upstream error; }只保留 error 类重试超时和 http_500 都不重试避免一个请求被多次处理。5. 验证结果与排查经验速查5.1 通过日志验证真实转发去向生产环境不方便随便停后端那怎么确认负载均衡在按预期工作最直接的方式是看 Nginx 的访问日志。默认日志格式里能看到请求的响应状态、耗时、来源 IP但看不到转发给了哪台后端。要看到这个信息需要自定义日志格式在 http 块或 server 块里定义 log_formatlog_format upstream $remote_addr - $upstream_addr [$time_local] $request $status $body_bytes_sent $http_user_agent $upstream_response_time;关键变量是 $upstream_addr它记录了请求实际被转发到的后端地址比如 192.168.1.12:8080。日志里如果长期只有某一台后端的地址说明流量倾斜如果三台交替出现那负载均衡就是在正常干活。我把这个 log_format 放到 nginx.conf 的 http 块里所有站点都能复用有需要的时候直接把日志路径指到这个格式很方便。5.2 常见问题速查表实践过程中我把高频问题整理成了一张速查表遇事可以直接对照问题现象大概率原因排查方向502 Bad Gateway后端服务没起或端口不对检查后端进程和监听端口确认 Nginx 与后端网络通504 Gateway Timeout后端响应时间超过 proxy_read_timeout调大 read 超时或检查后端慢查询请求全打到一台后端location 写错走了单台代理检查 location 匹配确认 upstream 名字引用正确后端拿不到客户端真实 IP缺少 proxy_set_header补 X-Real-IP 和 X-Forwarded-Forreload 后配置未生效语法错误被忽略或缓存先 nginx -t再 reload再确认版本3台后端轮询个别请求失败max_fails 判定失败摘除中查看 error.log确认是否在 fail_timeout 窗口内5.3 一个典型排障实录分享一次真实排障经历。有次线上反馈说某个接口偶发 502我第一反应是看 Nginx error.log。日志里能看到类似 connect() failed (111: Connection refused) while connecting to upstream 的记录。这说明 Nginx 尝试连接后端时连接被拒绝。接着我去看后端的连接数和进程状态发现后端 Tomcat 的线程池被打满新的连接直接排队触发拒绝。这个问题的根源不是负载均衡配置而是后端容量不足。Nginx 只是尽到了“发现后端不可用”的职责停止继续转发请求给故障节点。但我的 max_fails2 在这个场景反而加重了问题只要连续失败两次Nginx 就把节点摘除 30 秒期间流量全打到另一台另一台也很快被打满。后来我调整了策略把 fail_timeout 缩短同时在后端增加了健康检查的动态摘除手段整体稳定性才上来。这个案例想说的一点是Nginx 参数的取值直接影响故障行为。不是参数越多越高深越好而是要匹配后端的真实容量和故障恢复速度。脱离业务场景谈配置全是空谈。5.4 监控与告警建议负载均衡配置写完不算完事还要有盯住它的手段。最简单实用的方式是盯 Nginx 的 stub_status 模块它在 location 里暴露一个内置状态页location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; }访问 /nginx_status 能看到当前活跃连接数、请求总数、读写等待连接数。配合 Zabbix、Prometheus 这类工具可以周期抓取这些数据画出趋势图。后端每台机器的负载和日志也要一起看着因为负载均衡只能分流量不能消故障真正兜底的是后端服务的健康度。我个人在实际操作中的体会是负载均衡配置最容易出的问题往往不在配置本身而在“参数不匹配业务”。权重设得太激进、健康检查阈值太灵敏、超时时间卡得太死都会导致正常流量被误伤。上线前最好模拟一遍故障场景停一台机器、延迟几秒响应看看 Nginx 会做出什么反应这个过程非常值得做。最后再分享一个小技巧每次改完 Nginx 配置先备份一份带日期的副本再 reload。比如 cp loadbalance.conf loadbalance.conf.bak-20260101万一改乱了回滚也方便。别嫌麻烦这个习惯在关键时刻能救你一命。Nginx 配置这件事重要的不只是会写更要会安全地改、可追溯地改。