Nginx 502 故障排查:三行配置导致 keepalive 失效与连接复用问题

发布时间:2026/10/20 5:21:14
Nginx 502 故障排查:三行配置导致 keepalive 失效与连接复用问题 做运维这些年遇到过的Nginx 502不算少但大部分时候靠看错误日志→查后端服务→重启大法三板斧就能混过去。直到那次线上API网关在高峰期频繁502时好时坏持续了几个小时几十个业务方在群里艾特我。我前前后后花了将近3个小时最后发现罪魁祸首不是我一开始怀疑的后端服务也不是网络波动而是Nginx配置里三行不起眼的设置。这篇文章就把完整的排查链路、底层原理和修复方法写出来给以后遇到同类问题的朋友一个参考。1. 故障现象502在下午两点半准时“打卡”1.1 从业务报警开始的问题描述那天下午两点半左右监控平台开始刷屏某对外API网关的成功率掉到85%以下接口大面积返回502 Bad Gateway。受影响的不只一两个接口几乎所有走这个网关的请求都有概率中招。更诡异的是这种状态并不是持续的——稍微过个几分钟成功率又自己拉回99%以上然后再过十几分钟又掉下去。业务方的反馈是一会好一会坏没法用。我当时的第一反应是后端服务可能在做发布或者重启。毕竟502最常见的含义就是Nginx作为反向代理无法从上游服务器拿到有效响应。但查了一圈后端集群的进程启动时间全都很干净没有人做过发布也没有进程重启记录。1.2 为什么我一开始没当回事坦白讲刚开始我是有点松懈的。因为这类间歇性502我此前见过太多次十有八九是后端某个实例又OOM被系统杀了或者健康检查的探针路径写错了。我甚至还在群里回了句我先看下后端状态应该是某个节点的问题。结果这一看就是俩小时。1.3 规律浮出水面间歇性与并发强相关在反复观察了大概半小时后我发现了一个不太对劲的规律502并不是均匀分布的它总是在流量往上冲的时候出现。比如QPS从800涨到1500的时候成功率就会突然掉下来等流量回落一切又恢复正常。更奇怪的是从Nginx的错误日志看每次出问题的时间窗口只有几十秒不会持续太久。这个规律很重要它基本排除了后端进程挂掉这种持续性故障把嫌疑指向了某个资源在并发升高时被瞬间打满。但到底是什么资源被打满我当时还没有概念。按照我惯用的排查顺序下一步应该上日志了。2. 前两小时沿着最常规的路子排查2.1 第一步Nginx 错误日志里的关键线索我习惯先看/var/log/nginx/error.log因为它是整个排障链路里信息量最大、也最诚实的文件。那次也一样错误日志里刷满了两类报错connect() failed (111: Connection refused) while connecting to upstream connect() failed (110: Connection timed out) while connecting to upstream这两行字几乎就是502的标准答案——Nginx在建立到上游服务器的TCP连接时要么被对方直接拒绝111要么一直等不到握手成功110。所以问题一定出在Nginx到后端这一段链路上。当时我做了两个动作先看Nginx到后端服务器的网络是否通畅再确认后端端口是否在监听。分别在Nginx机器上执行了telnet 后端IP 8080以及在后端机器上执行ss -lntp | grep 8080结果很讽刺Nginx这边telnet能通后端那边端口也在正常监听。也就是说在单独测试的场景下链路完全正常。2.2 第二步后端服务明明活着curl 一切正常为了排除后端应用自身的问题我直接在后端机器上curl了本地的健康检查接口curl -i http://127.0.0.1:8080/health返回200 OK响应体是一个再正常不过的JSON。顺手看了下JVM的GC情况、线程数、CPU使用率也都在合理区间。最迷惑的就是这里从后端服务自身的视角来看它完全是健康的。可Nginx这边偏偏连接不上它。那问题有没有可能在Nginx所在的机器上我检查了Linux连接跟踪表conntrack和文件描述符使用情况全都没有异常。这个时候我已经在这类常规检查上花了差不多一个小时进度几乎是零。2.3 第三步监控数据自相矛盾的迷惑现场接下来我打开了后端服务的监控面板想看它在502发生时段的表现。结果很有意思在502出现的那个时间窗口里后端的TCP连接数瞬间冲高active connections从几百跳到两三千但紧接着又掉下来。CPU、内存、GC完全平稳没有任何瓶颈特征。那一刻我产生了一个猜测会不会是后端处理请求的速度跟不上导致大量请求在Nginx侧排队随后这个猜测也被推翻了——如果真是后端处理慢错误日志里应该更多出现upstream timed out这类读响应超时而不是connect() failed。关键是connect这一步都没成功说明连TCP连接都没建立起来压根谈不上处理慢。这个矛盾点让我的排查方向发生了一次重要转变问题大概率不在后端进程的处理能力上而在连接建立这个环节。要么是Nginx所在机器的临时端口被耗尽了要么是后端机器的accept队列被打满了。2.4 中间还踩了重启Nginx这个“有效但治标”的坑排查到中途因为业务方催得紧我先做了一次Nginx reload。生效之后故障确实立刻消失了大概正常了15分钟。但等我回过头继续看日志报错又出现了。这个现象当时差点把我带偏——它太像配置变更后忘记reload导致旧配置生效的问题了。我还特意确认了配置文件的md5值确认真实配置和预期一致。后来才明白重启也好、reload也好本质上只是把Nginx到上游之间积累的一堆异常连接全部清空了短暂恢复了连接池但生成这堆异常连接的根因纹丝不动所以问题很快又会长回来。也就是从这一刻起我决定停下来不再做任何外科手术式的操作老老实实复现问题。3. 第三小时压力复现与 netstat 暴露的真相3.1 用压测脚本复现线上故障既然线上现象是间歇性的最好的办法就是造流量把它复现出来。我拉了两台用于压测的机器用wrk加了一个模拟线上请求分布的脚本从低并发开始逐级往上升wrk -t8 -c300 -d60s --timeout 3s http://网关域名/api/xxx在300并发、60秒的测试里成功率从99%慢慢开始出现零星502。当我把并发数提到500后502的比例开始快速上升而且表现和线上几乎一模一样一阵一阵地出现不会持续。这里补充一个经验压测复现的时候千万不要一上来就发超大流量。那样虽然也会触发502但会把系统和网络层面的问题全部放大反而掩盖真正的诱因。从200、300、500这样一个台阶一个台阶往上加更容易观察到故障的临界点。3.2 连接数曲线只涨不跌的 TIME_WAIT在压测的同时我在Nginx机器上开启了两个实时观测窗口一个看Nginx到后端的连接状态统计一个看系统全局的TCP连接情况watch -n 1 ss -ant | grep 后端IP | awk {print $1} | sort | uniq -c watch -n 1 ss -s结果让我一下子清醒了。正常情况下Nginx到后端会有几十个ESTABLISHED连接这就是keepalive连接池已经建立并复用的表现。但这个生产环境里ESTABLISHED几乎看不到反而是TIME_WAIT的数量在持续累积一度突破了四位数。TIME_WAIT本身不是洪水猛兽它是TCP四次挥手后主动关闭连接的一方必经的状态几秒钟后就会被系统回收。但当它疯狂堆积说明线上正在高频地建立连接、又立刻断开连接。这压根不是连接池该有的表现更像是在搞每请求一条新连接的原始模式。3.3 找到核心嫌疑upstream 连接根本没有复用到这一步连接是否被复用成了整个排查的核心问题。我直接执行了下面这条命令统计当前Nginx与后端之间存在的连接总数ss -ant | grep -c 后端IP结果只有个位数。而我们已知这个网关的QPS在高峰时超过1500一个请求如果对应一条新建连接那后端在1秒内要处理1500次TCP握手还要在响应后主动断开连接。这对任何一台机器来说都是巨大的负担连接一拥堵Nginx就会开始报111或110502自然就出来了。可问题来了我记得网关的nginx.conf里明明配置了keepalive为什么实际没有复用为了确认配置没有被人为改动过我把线上生效的conf完整拉下来对比upstream backend_cluster { server 后端IP1:8080 max_fails3 fail_timeout10s; server 后端IP2:8080 max_fails3 fail_timeout10s; keepalive 32; }配置确实有keepalive 32;这一行。那它为什么失效了答案就在proxy_pass的location块里。4. 根源分析keepalive 配置为什么形同虚设4.1 配置里明明写了 keepalive却没生效我贴一下当时location块里跟上游通信相关的配置location /api/ { proxy_pass http://backend_cluster; 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_http_version 1.1也没有proxy_set_header Connection 。而这两个配置恰恰是Nginx与上游之间启用连接复用的前置条件。也就是说upstream里的keepalive 32;只是告诉Nginx你可以在空闲连接池里缓存32条连接到后端的keepalive连接但真正建立连接时Nginx默认用HTTP/1.0协议和后端通信而没有持久连接语义的HTTP/1.0请求后端在处理完响应后就会把连接关闭。连接池里缓存连接的动作还没执行连接就已经断了。keepalive指令因此名存实亡。4.2 HTTP/1.0 与 HTTP/1.1 的持久连接差异这里我尽量用大白话讲一下底层逻辑。HTTP/1.0协议里每个TCP连接默认只处理一个请求服务器响应完就直接关闭连接除非客户端显式加一个Connection: keep-alive头双方才可能把连接保留下来。HTTP/1.1协议则把持久连接变成了默认行为只要没有Connection: close头TCP连接就可以被多个请求复用。而Nginx向后端转发请求时默认使用的是HTTP/1.0。在这种情况下即便upstream块里写了keepalive连接也无法进入空闲可复用队列。Nginx和后端的交互过程就变成了一个请求进来新建TCP连接完成HTTP请求响应连接立刻关闭。下一个请求再重复一遍。这个差异在高并发场景下是致命的。短连接模式下QPS每往上走1000每秒就要新增1000次TCP三次握手和四次挥手。TCP握手需要时间TIME_WAIT会堆积后端机器的accept队列会被打满最终表现就是Nginx在connect阶段就失败。4.3 源端口耗尽从 TCP 层面理解 502 的最后一击还有一个更隐蔽的助推因素源端口耗尽。Nginx作为发起连接的一方每次向后端建立新连接时都会占用本机一个临时端口。Linux系统默认的临时端口范围通常是从32768到60999也就是大约28000多个可用端口。前面说过了TIME_WAIT状态的连接要等大约60秒才会被系统回收如果新建连接的速度远远快于回收速度临时端口很快就被TIME_WAIT连接占满。一旦源端口耗尽Nginx再想向后端发起新连接系统直接返回Cannot assign requested address。这个错误会同样体现为502。我就是在这个环节彻底确认了整个链路配置缺三行连接退化成短连接短连接导致TIME_WAIT堆积和源端口耗尽最终表现为间歇性502。5. 修复与验证三行配置解决战斗5.1 最终修复配置修改后的location块是这样location /api/ { proxy_pass http://backend_cluster; proxy_http_version 1.1; proxy_set_header Connection ; 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_http_version 1.1;让Nginx改用HTTP/1.1协议与上游通信这样连接具备持久化能力。第二行proxy_set_header Connection ;则是清空Nginx向后端传递的Connection头避免客户端请求里自带的Connection头比如WebSocket升级请求里的Connection: Upgrade被原样透传给后端干扰连接复用逻辑。那为什么keepalive数字建议保持32或者适当调大因为空闲连接池本身也是在缓存与后端的连接这些连接虽然空闲但仍占用后端的线程和文件描述符资源。数值设太大比如200反而可能把后端的连接数打高。32在实际运行中是一个相对平衡的起步值。5.2 验证结果TIME_WAIT 断崖式下降执行nginx -t检查配置语法没问题后我做了reload然后用之前同样的压测方式重新打了500并发。几组数据对比非常直观指标修复前500并发修复后500并发502错误率约7%-12%0%Nginx到后端连接数个位数稳定在25-35之间TIME_WAIT连接数峰值超过4000峰值降到200左右请求平均响应时间部分请求超3s稳定在100ms以内压测稳定跑了5分钟一个502都没出现。随后流量逐渐切回生产原来那种每到高峰就掉成功率的现象彻底消失。Nginx到后端的ESTABLISHED连接数稳在了keepalive池设置的32附近TIME_WAIT数量也降到了一个非常健康的水平。5.3 回滚预案如果不能动后端怎么办这里有个特殊场景我也想提一下如果后端服务本身是老旧系统只支持HTTP/1.0升级到1.1反而可能导致兼容问题。这种情况下可以考虑保持HTTP/1.0但必须从其他层面控制连接建立频率比如调大proxy_connect_timeout在可接受范围内、规划更分散的上游节点、或者在Nginx前再加一层负载分发来降低单机连接压力。不过这些都是绕路方案最根本的解法依然是让连接能复用。我还额外确认过这个场景下并不需要调keepalive_timeout、keepalive_requests这类和客户端侧相关的指令因为问题只发生在上游通信段。很多时候配置调优失控就是因为分不清这个keepalive属于连接池还是属于HTTP连接的空闲超时把它俩混为一谈结果越调越乱。6. 这次故障教会我的Nginx 连接复用配置自查清单6.1 快速自查清单我把这次故障的排查经验整理成了一份检查清单每次写新的Nginx反代配置或线上出现诡异502时我都会按顺序过一遍upstream块里是否写了keepalive且数量是否符合预期对应location块里是否有proxy_http_version 1.1对应location块里是否显式清空了Connection头代理的协议是HTTP还是HTTPS、WebSocket不同协议对连接的要求不一样后端服务本身是否支持长连接如Tomcat、Spring Boot等容器对HTTP/1.1的支持通常是默认开启的但某些自研框架或老旧中间件可能默认关闭如果配置里keepalive写了、但上面两条漏了那等于没写。这是我这次踩坑后最刻在脑子里的记忆。6.2 排查 502 的一线经验顺序经历过这次之后我处理502的排查顺序也固定了下来先看Nginx日志确定报错类型是connect阶段还是read阶段。connect阶段的问题优先查网络连通性、后端监听端口、连接池复用、源端口耗尽read阶段的问题优先查后端处理耗时、超时配置、响应缓冲区。然后立即观察Nginx和后端之间的连接状态确认连接是否在复用。如果连接数稀少而QPS很高基本可以锁定短连接问题。还有一个很实用的方法直接在Nginx机上抓包过滤。比如用tcpdump抓后端端口的TCP握手包如果每秒SYN包数量异常高就能从底层佐证每请求都在新建连接。这个方法在大规模生产环境里尤其好用比反复看监控图表更直观。6.3 最后一个反直觉的提醒最后分享一个反直觉但很重要的点问题爆发时会想当然地加机器、加线程池但那次的事实是后端机器的CPU、内存完全没到瓶颈加机器只能解决表象治标不治本。另一点是任何一次reload或重启让故障短暂消失都值得警觉。它说明系统里积累的异常状态被重置了但生成异常状态的机制还活着很快就会复发。遇到这种情况宁可顶着压力把根因找出来也不要反复使用重启大法。说到底Nginx的502排查并不一定非要用多高深的手段很多时候就是一层窗户纸。但这层窗户纸如果没捅破可能让大家在线上耗掉一个又一个下午。希望这篇复盘能帮各位少走一次冤枉路至少在打开nginx.conf的时候多看两眼那三行不起眼的配置。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询