
做Nginx反向代理的同学一定都见过那行刺眼的502 Bad Gateway白色页面。无论是刚上线的新服务还是跑了几年的老网关502出现的瞬间用户的投诉、领导的电话跟着就来。这篇文章我不打算讲那些遍地都是的基础教程而是把我自己在几个业务线里排查502的真实路径、遇到的高频坑、以及最终落地的预防手段全部摊开按“看懂错误—系统排查—根因修复—长效预防”的顺序写下来希望你在Nginx反代报502时能少走几轮弯路。1. 502 Bad Gateway的本质先搞懂错误从哪里来1.1 一次反向代理请求的完整生命线先说个最简单的场景用户浏览器发起请求Nginx作为反向代理收到请求后把流量转发给后端应用服务器——比如Java的Tomcat、Python的Gunicorn、Node的PM2进程。整个交接过程大致是四步浏览器把请求发到Nginx的监听端口通常80或443Nginx根据location和proxy_pass配置选择一个upstream后端IP和端口Nginx向后端发起TCP连接发送HTTP请求后端处理完成后返回HTTP响应Nginx再把响应原样交还给浏览器。502发生的位置就在第3步到第4步之间。更准确地说Nginx已经尝试向后端发起通信但要么连接根本建立不起来要么后端返回了一个在Nginx看来无效的响应要么响应头还没读完连接就断掉了。Nginx没办法把有效的响应头回给用户于是只能自己从5xx里挑一个502状态码返回。所以502的全称叫Bad Gateway网关Nginx和网关后面的服务后端之间通信出了问题。问题一定出在“Nginx与后端之间的这一跳”上而不是Nginx自身静态文件服务出了问题。搞懂这一点你就不会在用户浏览器、DNS解析、CDN这些八竿子打不着的地方浪费时间。1.2 502、503、504到底有什么区别很多同学分不清这三个状态码排查时容易走冤枉路。我做个简单的区分状态码含义本质差异常见典型病因502网关收到无效响应或拿不到响应后端给出的响应在Nginx看来是无效的后端没起来、连接被拒、响应被截断503服务不可用Nginx本身或upstream整体没有可用节点维护中、权重全为0、瞬间过载504网关超时Nginx等后端等太久了自己先放弃后端处理太慢、SQL卡死、线程池耗尽这个区分特别重要。因为502的排查方向聚焦在“连接与响应完整性”504主要聚焦“超时与性能”。如果错误日志里写着Connection refused那大概率是后端端口根本没有服务在监听如果写着timed out那方向就要转向网络连通性和后端慢查询。2. 系统化排查路径别上来就改配置2.1 第一件事永远是翻Nginx错误日志我见过太多人一看到502第一反应是打开nginx.conf改proxy_pass改完reload发现还是502来回折腾一上午。其实Nginx早就把原因写在日志里了你只需要去看。# 看当前nginx使用的配置文件路径 nginx -t # 查看error_log配置 nginx -T 2/dev/null | grep error_log # 默认通常在 /var/log/nginx/error.log tail -f /var/log/nginx/error.log错误日志里针对502最典型的几种报错我摘出来逐条看。第一条2024/06/12 10:23:01 [error] 12345#0: *789 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.1.100, server: www.example.com, request: GET /api/order HTTP/1.1, upstream: http://127.0.0.1:8080这一条信息量已经很大upstream是127.0.0.1:8080后端8080端口拒绝了连接。方向直接锁定检查8080端口到底有没有服务在监听。第二条2024/06/12 10:24:11 [error] 12345#0: *790 upstream prematurely closed connection while reading response header from upstream, upstream: http://127.0.0.1:9000这一条是“后端把连接过早关闭了”。后端确实接受了连接但还没返回完整的响应头就断了。常见原因是后端进程崩溃、被OOM killer杀掉、或者后端业务代码在返回响应之前异常退出。第三条2024/06/12 10:25:00 [error] 12345#0: *791 no live upstreams while connecting to upstream, upstream: http://backend_pool这一条说明upstream配置的健康节点数为0所有后端节点都被标记为不可用。这种在负载均衡场景下很典型后面我专门讲。2.2 三层验证Nginx配置、后端状态、网络链路日志给了方向但还不够最好用三层验证把怀疑坐实。第一层Nginx侧。确认Nginx配置没有语法问题、proxy_pass路径对不对。运行nginx -t检查再用curl直接访问Nginx的对外入口观察返回状态。第二层后端侧。绕过Nginx直接访问后端服务看后端本身是不是正常的。这一步能区分问题出在Nginx还是后端。第三层网络链路。确认Nginx所在机器到后端所在机器的网络通不通中间有没有防火墙、安全组拦截。特别是云服务器环境同一台机器还要注意SELinux和iptables。2.3 完整排查命令清单整理一份我自己常用的排查顺序直接复制到服务器一行行执行# 1. 检查Nginx配置 nginx -t # 2. 检查Nginx进程 ps -ef | grep nginx # 3. 直接curl Nginx入口 curl -I http://127.0.0.1:80 # 4. 检查后端端口监听 ss -lntp | grep 8080 # 5. 直接curl后端绕过Nginx curl -I http://127.0.0.1:8080 # 6. 如果后端在别的机器检查连通性 telnet 192.168.1.10 8080 # 7. 检查iptables和selinux iptables -L -n | grep 8080 getenforce这套命令走下来绝大多数502的根因已经浮出水面剩下的就是针对具体根因去修。3. 六大高频根因与实操解法3.1 根因一后端服务没起来或端口没监听最朴实但也是出现频率最高的原因后端应用崩了、没启动、或者启动后端口没监听。体现在日志上就是connect() failed (111: Connection refused)。处理步骤# 确认端口 ss -lntp | grep 8080 # 如果没有任何输出说明后端应用没有监听8080去后端日志找启动失败原因 journalctl -u your-app -n 50 systemctl status your-app如果是PHP-FPM场景502的经典原因是php-fpm进程死亡或者socket文件丢失日志通常长这样connect() failed (111: Connection refused) while connecting to upstream, upstream: fastcgi://127.0.0.1:9000解决办法是重新拉起php-fpmsystemctl start php-fpm这里的坑在于php-fpm可能因为pm.max_children设置过小请求一多全部被占满新请求直接被拒。这种情况光重启不解决要调pm.max_children、pm.start_servers。我遇到过一个真实案例业务高峰期每分钟1000请求pm.max_children只给了8502每小时准时出现调成32后彻底消失。3.2 根因二proxy_pass路径拼接错位路径问题不容易一眼发现因为Nginx配置完全合法nginx -t不报错但访问后端的实际URL和后端路由对不上后端返回404严重时连接被重置Nginx兜底给了502。先记住两个关键规则。不带URI的写法保留原始URIproxy_pass http://127.0.0.1:8080;带URI的写法用URI替换匹配部分proxy_pass http://127.0.0.1:8080/api;举个例子。用户访问的是https://www.example.com/bbs/login配了location /bbs { proxy_pass http://127.0.0.1:8080; }后端收到的是/bbs/login注意开头斜杠没丢。但如果配的是location /bbs { proxy_pass http://127.0.0.1:8080/; }后端收到的是/login/bbs前缀被替换掉了。很多老项目前后端路由是严格绑定的少了或多了prefix后端框架直接404或400。排查办法很简单打开Nginx的access.log看$request_uri和upstream_status前后对照一次就明白。3.3 根因三防火墙和安全组拦截当日志出现connect() failed (113: No route to host)或(111: Connection refused)且确认后端端口在监听时大几率是防火墙在拦。云服务器场景尤其要查两个地方Linux本机的firewalld或iptables云厂商控制台的安全组规则。我之前在云ECS上踩过一回后端部署在另一台内网机器上telnet内网IP 8080根本不通最后发现是安全组里忘了放行“内网入方向的8080端口”只放行了从外部访问的80端口。加上后502秒变200。检查命令firewall-cmd --state firewall-cmd --list-ports iptables -L -n还要注意SELinux。CentOS/RHEL系列默认enforcing模式下Nginx连接后端端口可能被拒日志表现又不太像防火墙。临时验证办法setenforce 0如果关掉后502消失那就是SELinux策略问题需要放行setsebool -P httpd_can_network_connect 13.4 根因四缓冲区大小不够导致upstream提前断开这种502的症状很诡异小接口正常大接口随机502本地curl后端一切正常走Nginx就偶发失败。日志里会出现upstream prematurely closed connection while reading response header from upstream根本原因是响应头太大超出了proxy_buffer_size的容量Nginx读不完响应头而socket缓冲又放不下触发重置。默认proxy_buffer_size通常是4k或8k如果后端返回的Set-Cookie、Token、自定义响应头很大就会顶爆。解法location /api { proxy_pass http://127.0.0.1:8080; proxy_buffer_size 16k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; }我总结的调参经验先把proxy_buffer_size提到16k看502是否消失。如果响应头特别大再往上加。别忘了FastCGI场景要用fastcgi_buffer_size和fastcgi_buffers。3.5 根因五超时配置太紧502里也混着一部分其实是超时引起的尤其在后端处理时间波动较大的场景。Nginx和后端之间的三个超时参数对应三个阶段参数对应阶段默认值proxy_connect_timeout建立连接60秒proxy_send_timeout发送请求60秒proxy_read_timeout等待响应60秒如果后端接口本身就要跑几十秒而代理层默认的read_timeout还是60秒那请求基本一到临界点就被Nginx掐断。报表导出、批量数据处理这类接口特别容易踩。合理配置location /report { proxy_connect_timeout 5s; proxy_send_timeout 10s; proxy_read_timeout 300s; }注意connect_timeout不要设太大5秒足够真正应该放宽的是read_timeout因为后端计算需要时间。我见过有人把三个都设成600s结果后端宕机时Nginx也傻等十分钟体验极差。连接建立失败要快速失败读响应可以耐心等。3.6 根因六keepalive配置不完整这是最容易被忽略的。很多生产环境为了性能在upstream里加了keepalive但没配proxy_http_version和Connection头导致客户端复用和后端的长连接失效出现间歇性502或者连接被重置。正确的长连接配置样板upstream backend_pool { server 127.0.0.1:8080 weight1 max_fails2 fail_timeout30s; keepalive 32; } server { listen 80; location / { proxy_pass http://backend_pool; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里的关键是proxy_http_version 1.1和proxy_set_header Connection ;两者缺一不可。缺少Connection头后端可能认为这是短连接处理完就关闭Nginx复用到一半发现连接已失效客户端就看到502。keepalive不要乱调大我一般是32起步再根据worker_connections和后端并发模型微调。后端是单线程阻塞模型时keepalive太大反而占满后端线程池。4. 负载均衡和高并发场景下特有的502形态4.1 后端重启瞬间的雪崩单台后端没有这个问题但在upstream里有多个后端节点时只要其中一个节点重启处于该节点上的活跃连接会立即断开Nginx对这些请求返回502。如果重启操作是滚动进行的那502的窗口会更大。这里有三个动作可以显著减少窗口期后端进程停止前先通过负载均衡或者注册中心把它摘流Nginx的upstream配置里给每个节点设置合理的max_fails和fail_timeout后端应用启动后做健康检查通过后再把流量放回去。如果用的是Nginx Plus或者OpenResty可以依赖主动健康检查模块。开源版Nginx做不了真正意义上的主动探测除非编译第三方模块所以最实用的办法还是靠被动健康检查加后端优雅上下线脚本。4.2 max_fails和fail_timeout的设置默认情况下max_fails1fail_timeout10s。含义是10秒内失败1次就把这个节点标记为不可用接下来10秒都不再往这个节点转发流量。这个默认值看起来合理但高并发场景下你就会发现一次偶发的超时比如后端GC停顿就会触发摘除10秒内的流量全部压往其他节点其他节点可能因此过载雪崩继而也被摘除最后出现no live upstreams。我的建议是按业务特性调upstream backend_pool { server 192.168.1.11:8080 max_fails3 fail_timeout30s; server 192.168.1.12:8080 max_fails3 fail_timeout30s; keepalive 32; }max_fails设成3fail_timeout设成30s既不会因为小抖动立刻摘节点也不会让坏节点带病工作太久。4.3 长连接、健康检查与连接池的配合高并发502有一个非常隐蔽的形态后端一切正常但日志刷Connection reset by peer。这往往不是后端崩溃而是后端有连接数上限比如Tomcat maxThreads、Gunicorn的worker数、MySQL连接池Nginx复用连接过多过快把后端连接数打满了。排查思路是先看后端的连接数监控和错误日志确认是否有连接超限记录。再回头看Nginx的keepalive参数是否合理。如果后端的worker数只有10前端keepalive却配了64那高峰期必然出问题。把keepalive降到和后端worker数接近的水准比如16到24往往立竿见影。另外nginx_upstream_check_module这种主动健康检查模块可以在请求发起前就探活把不健康的节点摘掉。但编译配置比较麻烦如果要快速解决用被动健康检查配合监控告警也一样够用。4.4 后端响应慢引起的连锁502还有一种高并发场景容易被忽略后端响应变慢Nginx等待超时后主动断开但后端的实际业务还在继续执行数据库连接、线程、内存都被占用。等到Nginx侧认为这个节点fail了又把流量切到其他节点其他节点也开始变慢最终整个集群雪崩。面对这种问题光调大proxy_read_timeout解决不了根本反而会让Nginx占住更多连接。正确思路是把后端接口的P99耗时监控起来设置性能基线一旦响应变慢就告警让研发去优化接口本身。Nginx侧的timeout只能兜底不能救命。5. 生产环境的长效预防把502消灭在发生之前5.1 监控告警先于用户发现问题502一旦大面积出现用户感知是灾难性的所以告警必须先于用户。我在生产里给Nginx入口的每个server都加了状态码监控重点盯5xx比例。具体做法从access.log里用脚本统计每分钟5xx请求占比超过阈值比如1%触发告警告警信息自带时间范围和具体URI方便快速定位是哪个接口出了问题。如果access.log里没有upstream_status建议加上。log_format可以这样配置log_format access $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for upstream_addr$upstream_addr upstream_status$upstream_status request_time$request_time upstream_response_time$upstream_response_time;把upstream_addr和upstream_status打出来502发生的时候你能第一时间知道是哪个后端节点的问题而不是在多个节点之间瞎猜。5.2 优雅重启与上线流程后端要发布升级直接kill进程是502的最快产生方式。正确的优雅下线流程应该是在Nginx upstream里把该节点的weight调成0reload等该节点上的存量请求全部返回停止后端进程发新版本启动确认新进程健康后再把weight调回去reload。这个流程对没有注册中心的传统架构特别有用。用脚本把reload封装好上线集群成员时不要一次性全部替换要一批一批来保证任何时候都有可用节点。5.3 常见问题速查表最后把各类502的表现、日志特征和直接解法整理成一张表方便团队wiki留存日志关键词直接原因优先处理方向connect() failed (111: Connection refused)端口无服务启动或拉起后端服务connect() failed (110: Connection timed out)网络不通检查防火墙、安全组connect() failed (113: No route to host)链路不可达检查路由、安全组upstream prematurely closed connection响应被截断检查后端进程是否崩溃、缓冲区是否太小no live upstreams while connecting所有节点被摘除检查后端整体状态与max_fails设置Connection reset by peer连接被后端重置检查后端连接数、线程池是否打满排查顺序还是那句话先看error.log判断列别再按列别做对应处理而不是没有章法地改配置。Nginx reload之前先nginx -t确认语法避免改完配置又叠加一个新的503出来。这套流程我在实际业务里跑了很久也是被线上事故一轮一轮逼出来的。502本身并不可怕可怕的是没有排查思路东改一下西改一下把线上环境越搞越复杂。真到了处理502的时候第一件事把错误日志翻出来再按这里的路径一步步来大多数问题都能在十分钟内定位清楚。