
1. 502不是Nginx的错但排查必须从它开始线上服务突然大面积报502运维群里炸了锅这种场景我经历过不止一次。Nginx作为反向代理层它返回502 Bad Gateway本质上是在说一件事我成功接收了客户端的请求但在把请求转发给上游服务器的过程中上游给了我一个我无法理解的响应或者干脆连不上。注意这个措辞——Nginx本身通常不是故障的根源它更像是一个报信人。很多人一看到502就重启Nginx这基本等于把报警器砸了火还在烧。这篇文章适合谁看如果你正在经历502告警、曾经被502搞得焦头烂额、或者想提前建立一套系统的排查方法论那接下来的内容应该能帮到你。我会从Nginx返回502的底层触发条件讲起然后一步步展开排查链路包括怎么区分是上游服务挂了、连接池满了、超时设置不合理、还是DNS解析出了问题。每个环节我都会给出具体的命令、配置片段和判断依据尽量让你看完就能直接上手操作。先明确一个核心认知502的排查方向永远指向上游。Nginx的proxy_pass指向的那个后端服务才是你需要重点关照的对象。上游可能是Tomcat、Gunicorn、Node.js进程、PHP-FPM也可能是另一个Nginx。不管是什么排查的逻辑是相通的——先确认Nginx到上游的网络通不通再确认上游服务本身活没活着最后看上游处理请求的能力是否跟得上。我见过太多人一上来就翻Nginx的error.log看到connect() failed (111: Connection refused)就以为是Nginx配置错了。其实这条日志已经说得很清楚了Nginx尝试连接上游的某个端口被拒绝了。拒绝的原因可能是上游进程没启动、端口没监听、或者防火墙拦了。Nginx的日志只是告诉你“我连不上”至于“为什么连不上”得你自己去查。还有一个常见的误区把502和504混为一谈。502是“上游给了无效响应或连接失败”504是“上游在规定时间内没给响应”。虽然它们经常一起出现但排查方向有重叠也有差异。502更偏向连接层面的问题504更偏向处理耗时的问题。当然实际场景中两者可能同时出现比如上游服务过载既拒绝新连接又让已有连接超时。在展开具体排查步骤之前我想先强调一个原则先止损再定位最后复盘。线上故障当前第一优先级是恢复服务而不是找到根因。你可以先重启上游服务、扩容实例、切流量到备用集群把服务恢复了再慢慢查。但止损动作要记录否则复盘时你连自己做过什么都忘了。我习惯在故障处理时开一个文档每执行一个操作就记一笔包括时间、操作内容、执行人、结果。这个习惯救过我很多次。2. 从Nginx错误日志里读出上游的真实状态Nginx的error.log是排查502的第一手资料但很多人只看最后几行或者只搜“502”这个关键词这就浪费了日志里最有价值的信息。Nginx在返回502时error.log里通常会记录一条[error]级别的日志格式大致是这样的2024/01/15 14:23:45 [error] 12345#0: *67890 connect() failed (111: Connection refused) while connecting to upstream, client: 10.0.0.1, server: api.example.com, request: GET /v1/orders HTTP/1.1, upstream: http://10.0.0.5:8080/v1/orders, host: api.example.com这条日志里包含了几个关键信息我逐一拆解时间戳2024/01/15 14:23:45精确到秒。如果502是集中爆发你会看到大量相同或相近时间戳的日志。这能帮你判断故障是突发的还是渐进的。错误类型connect() failed表示连接阶段就失败了。如果是upstream timed out那是读取响应超时。如果是no live upstreams说明所有上游节点都被标记为不可用了。错误码(111: Connection refused)是操作系统层面的错误码。111通常意味着目标端口没有进程监听或者防火墙主动拒绝。如果是(110: Connection timed out)那是连接超时说明网络不通或上游响应极慢。上游地址upstream: http://10.0.0.5:8080/v1/orders这是Nginx实际尝试连接的后端地址。注意这里可能显示的是域名或IP取决于你的proxy_pass配置。客户端地址client: 10.0.0.1发起请求的客户端IP。如果大量502来自同一个客户端可能是那个客户端在疯狂重试。我一般会先用grep把502相关的日志捞出来按时间排序看看错误类型的分布grep 502 /var/log/nginx/error.log | awk {print $NF} | sort | uniq -c | sort -rn这条命令会统计每种错误信息出现的次数。如果Connection refused占大头那基本可以确定是上游服务没起来或者端口没监听。如果upstream timed out居多那问题出在上游处理太慢。如果no live upstreams频繁出现说明健康检查机制把上游全摘了。还有一个细节值得注意Nginx的error.log默认可能只记录error级别但有些连接问题会以warn级别记录。建议在排查期间临时把日志级别调到info这样能看到更多连接建立的细节。不过调完之后记得改回来否则日志量会爆炸。error_log /var/log/nginx/error.log info;调成info之后你会看到Nginx尝试连接上游的详细过程包括用了哪个上游节点、连接耗时多少、是否复用了长连接。这些信息对判断是连接池问题还是网络问题很有帮助。另外如果你用了Nginx的upstream模块做负载均衡还可以在日志里看到具体是哪个上游节点出的问题。比如upstream backend { server 10.0.0.5:8080; server 10.0.0.6:8080; }当其中一个节点挂掉时error.log里会显示具体是哪个IP和端口连接失败。这能帮你快速定位是单点故障还是全体故障。注意Nginx的error.log在高并发场景下写入量很大如果磁盘IO跟不上可能会丢日志。建议把error.log单独挂一块盘或者用syslog转发到远程日志服务器。3. 上游服务到底活没活着从端口到进程的逐层确认拿到Nginx的日志线索之后下一步就是登录到上游服务器上确认服务本身的状态。这个过程我习惯按“端口→进程→资源→应用日志”的顺序来查从外到内逐层深入。3.1 端口监听状态第一道门先看上游服务有没有在监听预期的端口。假设Nginx日志里显示的上游地址是10.0.0.5:8080那就登录到10.0.0.5上执行ss -lntp | grep 8080或者用netstatnetstat -lntp | grep 8080如果输出为空说明没有任何进程在监听8080端口。这时候要么是服务没启动要么是启动时绑定了错误的端口或IP。如果输出显示LISTEN但绑定的IP是127.0.0.1:8080而不是0.0.0.0:8080那Nginx从外部连过来就会被拒绝。这种情况在容器化环境里特别常见——服务在容器内监听了127.0.0.1但Nginx从容器外访问自然连不上。如果端口在监听但Nginx还是报Connection refused那就要检查防火墙规则了。iptables或firewalld可能拦了来自Nginx所在机器的流量。可以临时用telnet或nc从Nginx机器上测试连通性telnet 10.0.0.5 8080如果连不上但上游机器上ss显示端口在监听那基本就是防火墙或安全组的问题。云环境里还要检查安全组的入站规则很多人忘了放行Nginx所在网段。3.2 进程状态服务真的在跑吗端口在监听不代表服务健康。有时候进程还在但已经卡死或者陷入死循环无法处理新请求。这时候ps和top就派上用场了ps aux | grep 服务名看看进程的CPU和内存占用。如果CPU跑满说明服务在疯狂计算可能是某个请求触发了死循环或者线程池被耗尽。如果内存占用接近上限可能频繁GC甚至OOM。如果进程状态是D不可中断睡眠那通常是在等IO磁盘或网络可能有问题。我遇到过一次典型场景上游服务的进程还在端口也在监听但所有工作线程都被阻塞在一个慢查询上新请求进来后排队等待最终Nginx等不及返回了502。这种情况下ps看到的进程状态是正常的但top里能看到load average飙升而且waIO等待占比很高。3.3 系统资源被忽略的瓶颈上游服务所在机器的资源状况往往是最容易被忽略的排查环节。CPU、内存、磁盘、文件描述符、网络连接数任何一个到瓶颈都可能导致服务无法正常响应。# 查看CPU和负载 top -bn1 | head -20 # 查看内存 free -h # 查看磁盘使用率 df -h # 查看文件描述符使用情况 cat /proc/sys/fs/file-nr # 查看网络连接数 ss -s文件描述符耗尽是一个很隐蔽的坑。如果上游服务的ulimit -n设置得太小比如默认的1024在高并发下很快就会用完新连接无法建立Nginx自然收到502。检查方法# 查看进程的fd限制 cat /proc/pid/limits | grep open files # 查看当前进程打开的fd数量 ls /proc/pid/fd | wc -l如果打开的fd数量接近限制值那就需要调大ulimit并确保服务启动时继承了这个设置。注意ulimit的修改只对当前会话有效要永久生效得改/etc/security/limits.conf。3.4 应用日志最后的真相如果端口、进程、资源都正常但Nginx还是报502那就得看上游服务自己的日志了。应用日志里通常会记录请求处理的详细过程包括异常堆栈、超时信息、连接池状态等。比如Java应用可以看catalina.out或application.logPython应用看gunicorn或uwsgi的日志Node.js应用看pm2或forever的日志。重点搜ERROR、Exception、Timeout、Connection pool这些关键词。有一次我排查一个502问题Nginx日志显示upstream timed out但上游服务的应用日志里没有任何错误。后来发现是数据库连接池满了应用线程都在等数据库连接导致HTTP请求处理超时。这种问题在应用日志里可能只表现为处理时间变长不会直接报错需要结合数据库的监控来看。4. 连接池、超时与长连接Nginx侧配置的隐形陷阱排除了上游服务本身的问题之后如果502依然存在那就得回头看看Nginx的配置了。Nginx和上游之间的连接管理有一套自己的机制配置不当会直接导致502。4.1 proxy_next_upstream失败重试的边界Nginx默认在遇到error或timeout时会尝试下一个上游节点但这个行为是由proxy_next_upstream指令控制的。默认值是error timeout意味着连接失败或超时会重试。但如果你把它设成了off那一旦某个上游节点出问题Nginx就直接返回502不会尝试其他节点。proxy_next_upstream error timeout http_502 http_503 http_504;上面这个配置表示当遇到连接错误、超时、或者上游返回502/503/504时都尝试下一个节点。这在多节点负载均衡的场景下很有用能避免单点故障导致整体不可用。但要注意如果所有节点都挂了重试完一圈之后还是会返回502。还有一个坑proxy_next_upstream_tries和proxy_next_upstream_timeout这两个指令限制了重试的次数和总时间。如果设置得太小可能还没试到健康节点就放弃了。proxy_next_upstream_tries 3; proxy_next_upstream_timeout 10s;4.2 超时设置等太久和等不够都是问题Nginx和上游之间的超时主要有三个proxy_connect_timeout建立连接的超时时间默认60秒。如果上游响应慢可以适当调大但一般不建议超过5秒。proxy_send_timeout发送请求给上游的超时时间默认60秒。proxy_read_timeout读取上游响应的超时时间默认60秒。这个最常出问题如果上游处理请求需要很长时间而proxy_read_timeout设得太短Nginx就会断开连接并返回504不是502但经常和502一起出现。proxy_connect_timeout 5s; proxy_send_timeout 30s; proxy_read_timeout 60s;我一般会把proxy_connect_timeout设得比较短比如3到5秒。因为连接建立通常很快如果连不上等60秒也没用不如快速失败让Nginx有机会重试其他节点。proxy_read_timeout则根据业务特点来定如果是报表导出之类的慢接口可以设到300秒甚至更长。4.3 长连接与连接池keepalive的威力Nginx和上游之间默认使用短连接每个请求都要重新建立TCP连接。在高并发下这会导致大量TIME_WAIT状态的连接消耗端口资源甚至导致连接建立失败。开启长连接可以显著改善这个问题upstream backend { server 10.0.0.5:8080; server 10.0.0.6:8080; keepalive 32; keepalive_requests 1000; keepalive_timeout 60s; }keepalive 32表示每个worker进程保持最多32个空闲长连接到上游。keepalive_requests限制每个长连接最多处理多少个请求防止连接老化。keepalive_timeout是空闲连接的保持时间。但要注意开启长连接后上游服务也必须支持长连接。如果上游是HTTP/1.0或者不支持Connection: keep-alive那Nginx的长连接配置不会生效反而可能因为连接复用导致请求错乱。还有一个细节proxy_http_version必须设为1.1否则Nginx默认用HTTP/1.0和上游通信长连接无法建立。proxy_http_version 1.1; proxy_set_header Connection ;proxy_set_header Connection 这行很重要它会清除客户端传来的Connection头避免干扰Nginx和上游之间的长连接管理。5. 当502来自DNS解析被忽视的域名解析问题如果proxy_pass里写的是域名而不是IP那DNS解析也可能成为502的元凶。Nginx在启动时会解析一次域名然后把解析结果缓存起来。如果上游服务的IP变了但Nginx没有重新解析就会一直往旧IP发请求自然连不上。proxy_pass http://api.internal.example.com:8080;这种情况下如果api.internal.example.com的DNS记录变了Nginx不会自动更新。解决办法有两个一是用resolver指令配合变量来强制动态解析resolver 10.0.0.2 valid10s; set $backend http://api.internal.example.com:8080; proxy_pass $backend;注意用了变量之后Nginx每次请求都会重新解析DNSvalid10s表示缓存10秒。这会带来一定的性能开销但能保证IP变化后及时更新。另一个办法是直接用IP地址避免DNS依赖。但这样失去了灵活性适合上游地址固定的场景。DNS解析超时也会导致502。如果DNS服务器响应慢Nginx在解析域名时卡住连接建立就会超时。可以在Nginx机器上用dig或nslookup测试解析速度time dig api.internal.example.com如果解析耗时超过几百毫秒那就得考虑换DNS服务器或者改用IP了。6. 一套可复用的502排查清单与实战心得排查502最怕的就是东一榔头西一棒子没有章法。我把自己常用的排查流程整理成了一个清单按顺序执行基本能覆盖90%以上的场景。6.1 排查清单步骤检查项命令/操作判断依据1Nginx错误日志grep 502 /var/log/nginx/error.log看错误类型Connection refused / timed out / no live upstreams2上游端口监听ss -lntp | grep 端口是否有进程监听绑定IP是否正确3网络连通性telnet 上游IP 端口从Nginx机器测试能否连上4上游进程状态ps aux | grep 服务名进程是否存在CPU/内存是否正常5系统资源top、free -h、df -hCPU、内存、磁盘是否到瓶颈6文件描述符cat /proc/pid/limitsfd限制是否太小当前fd数是否接近上限7应用日志查看上游服务日志是否有异常堆栈、超时、连接池满8Nginx超时配置检查proxy_connect_timeout等是否设置过短或过长9长连接配置检查keepalive相关指令是否开启proxy_http_version是否为1.110DNS解析dig 域名解析是否正常耗时是否过长6.2 几个容易踩的坑坑一只看Nginx日志不看上游日志。Nginx的error.log只能告诉你“连不上”或“超时”但为什么连不上、为什么超时必须去上游找答案。我见过有人对着Nginx日志查了半天最后发现是上游服务的数据库密码错了导致服务启动后连不上数据库健康检查失败。坑二忽略TIME_WAIT堆积。如果Nginx和上游之间用短连接高并发下会产生大量TIME_WAIT状态的连接。ss -s可以看到TIME_WAIT的数量。如果超过几万可能会耗尽本地端口导致新连接无法建立。解决办法就是开启长连接或者调低net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle注意后者在NAT环境下有问题不建议用。坑三健康检查误杀。如果Nginx配置了max_fails和fail_timeout上游节点在短时间内失败几次后会被标记为不可用。如果健康检查的阈值设得太敏感比如max_fails1 fail_timeout10s那上游偶尔抖一下就会被摘掉导致所有请求都打到剩余节点上压力倍增。我一般会设max_fails3 fail_timeout30s给上游一点缓冲时间。坑四上游返回的响应头过大。这个比较冷门但确实遇到过。如果上游返回的响应头超过了Nginx的proxy_buffer_sizeNginx会返回502。默认proxy_buffer_size是4k或8k一般够用但如果上游返回了大量Set-Cookie或者自定义头就可能超限。解决办法是调大proxy_buffer_size和proxy_buffers。proxy_buffer_size 16k; proxy_buffers 4 16k; proxy_busy_buffers_size 32k;6.3 一个真实的排查案例某次线上服务大面积502Nginx错误日志显示大量connect() failed (111: Connection refused)上游地址是10.0.0.5:8080。登录到10.0.0.5ss -lntp显示8080端口没有监听。检查进程发现服务进程不存在。看服务日志最后一条是OutOfMemoryError进程被OOM Killer杀掉了。进一步查系统日志/var/log/messages确认是OOM Killer干的。原因是服务的内存限制设得太小而某个接口在处理大请求时内存暴涨触发了OOM。解决办法是调大内存限制同时优化那个接口的内存使用。从发现问题到恢复服务总共花了不到10分钟但如果没有按流程排查可能还在Nginx配置里绕圈子。这个案例说明502的根因可能离Nginx很远但排查的起点永远是Nginx的日志。顺着日志给的线索一层层往下查总能找到答案。6.4 预防胜于救火与其等502发生了再排查不如提前做好预防。我习惯在几个关键点加监控Nginx的upstream_response_time和upstream_connect_time如果这两个指标突然升高说明上游有问题。上游服务的进程存活监控进程没了立刻告警。上游机器的CPU、内存、磁盘、fd使用率监控。Nginx和上游之间的TCP连接数监控包括ESTABLISHED和TIME_WAIT。这些监控数据在排查时非常有用能帮你快速判断故障是突发的还是渐进的是单点还是全局。最后分享一个小技巧如果502是间歇性的很难复现可以在Nginx配置里临时加一个error_page把502请求的详细信息记录到单独的日志文件里error_page 502 log502; location log502 { access_log /var/log/nginx/502.log; return 502; }这样可以把所有502请求的完整信息包括请求头、上游地址、响应时间单独记录下来方便后续分析。不过这个配置会增加一点开销排查完记得去掉。排查502这件事说到底就是耐心加细心。Nginx已经把线索给出来了剩下的就是顺着线索往下挖。挖得多了自然就有手感了。