引言
502 Bad Gateway 是 Nginx 作为反向代理服务器时最常遭遇的错误状态码。这个错误意味着 Nginx 作为网关,成功与后端 upstream 建立了连接,但后端返回了一个无效响应,或者在传输过程中连接被异常关闭。表面上 502 只是一个 HTTP 状态码,实际上它是一系列配置错误、网络故障、后端异常的综合表现。
本文基于 Nginx 1.26.x/1.27.x(截至 2026 年 4 月的稳定版本)、PHP-FPM 8.2/8.3、Node.js 20 LTS/22 LTS 等最新稳定版本,系统性地拆解 502 错误的成因、排查方法、解决方案和预防策略。文章假设读者具备基本的 Linux 运维能力、Nginx 配置文件阅读经验,以及后端应用的调试基础。
一、问题场景:为什么需要关注 502 错误
1.1 502 是最常见的 Nginx 错误
在生产环境中,Nginx 作为入口网关承接所有外部流量。一旦出现 502 错误,意味着用户请求无法正常处理,直接影响业务可用性。根据多个大型网站的运维统计,Nginx 相关故障中 502 Bad Gateway 占到了 40% 至 60%,是所有 Nginx 错误码中出现频率最高的一种。
业务影响量化
用户点击链接后遭遇 502 页面,平均停留时长不超过 3 秒即会离开。对于电商网站,这直接导致购物车 abandonment;对于金融交易系统,502 可能意味着交易中断甚至数据不一致;对于 API 服务,502 会触发客户端重试风暴,进一步压垮后端。行业通用的估算公式是:每 1% 的 502 错误率可能导致 0.5% 至 2% 的业务损失,具体取决于业务类型和用户耐心度。
根因多样性与排查复杂性
502 错误的根因涵盖多个层面:网络层(物理链路中断、DNS 解析失败、防火墙拦截)、传输层(TCP 连接被重置、端口未监听)、应用层(后端进程崩溃、超时配置不合理、缓冲区溢出)、系统层(文件描述符耗尽、内存不足、内核参数限制)、安全层(SELinux/AppArmor 强制访问控制、iptables 规则拦截)。这种多层次性导致同一个 502 症状可能对应完全不同的根因。
多层代理架构进一步增加了排查难度。现代生产环境中,请求可能经过 CDN → 边缘 Nginx → 负载均衡层 → 应用 Nginx → 后端服务,其中任何一个节点的故障都可能以 502 形式呈现。而日志分散在多个层级,request_id 串联成为必要手段而非可选项。
日志阅读能力缺失是排查失败的主因
大量工程师遇到 502 后的第一反应是重启 Nginx 或后端服务,这种做法往往只能短暂恢复问题,无法根治。更深层的问题在于:Nginx error.log 中已经记录了详细的错误原因,但工程师不知道如何解读这些日志条目。例如"upstream prematurely closed connection"和"connect() failed (111: Connection refused)"虽然都导致 502,但指向的根因完全不同,处理方式也截然不同。
1.2 502 与 504:同样的网关错误,不同的根因
理解 HTTP 502 与 504 的本质区别,是准确定位问题的前提。
502 Bad Gateway
Nginx 向 upstream 发起请求并成功建立连接,但 upstream 返回了非正常的响应。这个响应可能是:连接建立后立即关闭(无任何数据返回)、返回了无效的 HTTP 响应(格式损坏)、连接被上游设备中断(RST 包)、或者 upstream 在协议层面出现错误(Nginx 无法解析上游的响应)。
典型错误日志:
2026/04/24 0832 [error] 12487#12487: *8921 upstream prematurely closed connection while reading response header of upstream, client: 203.0.113.45, server: api.example.com, request: "GET /api/v2/users HTTP/1.1", upstream: "http://127.0.0.1:8080/api/v2/users", upstream_connection: "9612", upstream_bytes_sent: 0, upstream_bytes_received: 0, upstream_trailers: "X-Request-Id: abc123"
2026/04/24 0918 [error] 12487#12487: *9234 connect() failed (111: Connection refused) while connecting to upstream, client: 198.51.100.23, server: www.example.com, request: "POST /api/orders HTTP/1.1", upstream: "http://127.0.0.1:9000/api/orders", upstream_connection: "128"
504 Gateway Timeout
Nginx 向 upstream 发起请求并成功建立了 TCP 连接,但 upstream 在规定时间内没有返回任何数据。超时触发后,Nginx 主动关闭连接并向客户端返回 504。504 的根因通常是后端处理时间过长(数据库查询阻塞、第三方 API 调用卡住、计算密集型任务卡死),或者 upstream 进程处于僵死状态(不崩溃但不处理请求)。
典型错误日志:
2026/04/24 1007 [error] 12487#12487: *10234 upstream timed out (110: Connection timed out) while reading response header of upstream, client: 203.0.113.89, server: api.example.com, request: "GET /api/reports HTTP/1.1", upstream: "http://127.0.0.1:8080/api/reports", upstream_connection: "15623", upstream_bytes_sent: 234, upstream_bytes_received: 0
2026/04/24 1133 [error] 12487#12487: *10891 upstream timed out (110: Connection timed out) after 30000 ms (110: Connection timed out) while connecting to upstream, client: 198.51.100.67, server: www.example.com, request: "POST /api/upload HTTP/1.1", upstream: "http://127.0.0.1:9090/api/upload"
499 Client Closed Request
这不是 Nginx 返回给客户端的错误码,而是 Nginx 记录的一种状态,表示客户端在 upstream 响应完成之前主动断开了连接(可能是客户端超时、用户取消请求、Nginx 侧配置了 client_body_timeout 或lingering_timeout 强制关闭)。499 不算 upstream 的故障,但在高并发场景下大量出现 499 可能暗示 upstream 处理速度过慢,迫使客户端等待超时。
2026/04/24 1245 [info] 12487#12487: *11567 client closed connection while waiting for response, client: 203.0.113.112, server: api.example.com, request: "GET /api/stream HTTP/1.1", upstream: "http://127.0.0.1:8080/api/stream"
500 vs 503 vs 502:细节差异
500 Internal Server Error:请求已经到达 upstream,upstream 在处理过程中发生了未捕获的异常或配置错误导致崩溃。这是 upstream 自身的问题,Nginx 只是转发了上游的错误响应。
503 Service Temporarily Unavailable:upstream 正常工作且能够响应,但 Nginx 的限流模块(limit_req_zone)或连接数限制(limit_conn_zone)触发了降级。503 通常带有 Retry-After 头,暗示客户端稍后重试。
502 Bad Gateway:upstream 连接失败或返回了无效响应。可能是 upstream 彻底不可用,也可能是连接建立后数据交换过程中断裂。
1.3 典型踩坑场景分类
在多年的生产环境排障经历中,502 错误可以归纳为以下典型场景。每个场景都有其特征性日志和明确的解决路径。
场景一:upstream 配置错误
最基础的配置错误是 ip:port 写错或域名解析失败。Nginx 启动时不会验证 upstream 的可到达性,只有在实际请求时才会尝试连接。如果配置中写错了 IP 地址(例如 127.0.0.18 而不是 127.0.0.1),或者端口号与实际监听不一致,Nginx 会在 error.log 中记录"Connection refused"错误。
# 错误配置示例 upstream backend { server 127.0.0.1:8081; # 实际服务运行在 8080 端口 } # 正确配置 upstream backend { server 127.0.0.1:8080; }
场景二:后端服务未启动
这是最常见也最容易被忽视的场景。Nginx 进程由 systemd 托管可以保证 Nginx 自身的高可用,但后端服务(PHP-FPM、Node.js 应用、Gunicorn、Tomcat 等)可能因为各种原因退出:OOM Killer 杀死进程、代码 bug 导致崩溃、依赖的数据库或中间件不可用。Nginx 仍然在运行,请求到达后才发现 upstream 无响应。
# systemctl 检查 PHP-FPM 状态 $ systemctl status php-fpm ● php-fpm.service - PHP FastCGI Process Manager Loaded: loaded (/usr/lib/systemd/system/php-fpm.service; enabled; vendor preset: enabled) Active: inactive (dead) since Thu 2026-04-24 0812 CST; 2h 15min ago Main PID: 4521 (code=exited, status=0/SUCCESS)
场景三:超时设置过短
Nginx 的默认超时值通常比较保守(proxy_connect_timeout 60s、proxy_read_timeout 60s)。对于处理时间较长的请求(如文件上传、大数据量导出、复杂数据库查询、调用外部 API),默认超时会导致正常请求被误杀。
# 默认超时配置(过短) proxy_read_timeout 60s; # 60秒对于复杂查询不够 # 优化后的超时配置 proxy_read_timeout 300s; # 根据业务特点调整 proxy_connect_timeout 10s; # 连接超时可以保持较短
场景四:缓冲区配置不当
Nginx 使用缓冲区来暂存 upstream 的响应数据。当 upstream 返回大文件或流式数据时,缓冲区配置不足会导致内存溢出或写入磁盘临时文件,极端情况下会触发 502。缓冲区满的症状是 error.log 中出现"upstream buffer is too small"或大量临时文件被写入 /var/lib/nginx/proxy。
# 缓冲区不足的典型日志 2026/04/24 1415 [warn] 12487#12487: *12891 an upstream response is buffered to a temporary file /var/lib/nginx/proxy/0000000123, size: 16384 bytes, expect: 5242880 bytes
场景五:keepalive 配置缺失
HTTP/1.1 默认支持 keepalive 连接复用。如果 upstream 块中没有配置 keepalive,Nginx 会为每个请求创建新的 TCP 连接。高并发场景下,频繁的 TCP 握手/挥手消耗大量资源,可能导致 upstream 过载或连接数溢出。
# 没有 keepalive 的 upstream 配置
upstream backend {
server 127.0.0.1:8080;
}
# 优化后:配置 keepalive 连接池
upstream backend {
server 127.0.0.1:8080;
keepalive 32; # 保持 32 个空闲连接
keepalive_requests 1000; # 每个连接最多处理 1000 请求
keepalive_timeout 60s; # 空闲连接保持 60 秒
}
场景六:权限问题
当 upstream 使用 Unix Domain Socket 通信时,socket 文件的权限和所有权必须与 Nginx worker 进程的用户匹配。PHP-FPM 默认使用 www-data 用户创建 socket,而 Nginx 默认使用 nginx 用户运行 worker 进程,两者不匹配时会导致"Permission denied"错误。
# /var/log/nginx/error.log 中的权限错误 2026/04/24 1527 [crit] 12487#12487: *13456 connect() to unix:/var/run/php-fpm/www.sock failed (13: Permission denied) while connecting to upstream
SELinux 强制访问控制是另一个容易忽视的权限问题。即使 socket 文件权限正确,SELinux 策略也可能阻止 Nginx 与后端通信。
二、核心原理与关键概念拆解
2.1 Nginx 处理请求的完整流程
理解 Nginx 内部处理请求的各个阶段,是准确定位 502 根因的基础。Nginx 采用模块化架构,请求处理分为 11 个阶段(phase):post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、precontent、content、log。502 错误主要发生在 content 阶段和 log 阶段之间。
连接接受阶段
Nginx master 进程监听配置的端口(80/443),worker 进程通过 accept_mutex 机制竞争接受新连接。在多核系统中,Nginx 默认使用 epoll 事件模型高效处理大量并发连接。关键配置参数:
worker_connections:每个 worker 进程可处理的最大连接数(默认 512)
multi_accept on:每个 worker 进程尽可能多地接受新连接
use epoll:使用 epoll 事件模型(Linux 高效)
# 查看当前 Nginx 连接状态
$ ss -s
Total: 298 (kernel 0)
TCP: 185 (estab 142, closed 12, orphaned 0, synrecv 0, timewait 5/0)
Transport Total IP IPv6
* 298 - -
RAW 0 0 0
UDP 12 8 4
TCP 173 166 7
INET 185 174 11
FRAG 0 0 0
$ nginx -V 2>&1 | grep -o'with-http_stub_status_module'
with-http_stub_status_module
# 启用状态页面查看连接详情
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
$ curl http://127.0.0.1/nginx_status Active connections: 142 server accepts handled requests 1289345 1289345 3456782 Reading: 3 Writing: 12 Waiting: 127
请求解析与 Location 匹配
连接接受后,Nginx 解析 HTTP 请求行和请求头,根据 server_name(基于 Host header 匹配)和 location 块(精确匹配或正则匹配)确定请求的处理方式。对于反向代理配置,请求最终会进入 proxy_pass 或 fastcgi_pass 处理。
# nginx.conf 中的 server 块和 location 匹配示例
server {
listen 80;
server_name api.example.com;
# 精确匹配 /api/
location = /api/ {
proxy_pass http://backend_api;
}
# 前缀匹配 /api/v2/
location /api/v2/ {
proxy_pass http://backend_v2;
}
# 正则匹配(~ 区分大小写,~* 不区分大小写)
location ~* ^/api/vd+/users {
proxy_pass http://backend_users;
}
}
代理转发阶段
请求匹配到 proxy_pass 或 fastcgi_pass 后,Nginx 作为反向代理与 upstream 通信。反向代理的核心流程:
解析 proxy_pass 指令,确定 upstream 地址
从 upstream 块中选择一个 server(默认加权轮询)
建立与 upstream 的 TCP 连接(触发 connect 事件)
发送 HTTP 请求(可能受限于 send_timeout)
接收 upstream 响应头(受限于 proxy_read_timeout)
接收 upstream 响应体(受限于 buffer 配置)
将响应转发给客户端
# 核心 proxy 配置解析
location / {
proxy_pass http://backend; # 指向 upstream 块
# 以下请求头默认会被传递
# Host: $host
# X-Real-IP: $remote_addr
# X-Forwarded-For: $proxy_add_x_forwarded_for
# X-Forwarded-Proto: $scheme
proxy_http_version 1.1; # 默认 1.0,1.1 支持 chunked 编码
proxy_set_header Host$host;
proxy_set_header X-Real-IP$remote_addr;
proxy_set_header X-Forwarded-For$proxy_add_x_forwarded_for;
}
响应处理与缓冲区机制
Nginx 收到 upstream 响应后,先将响应头存入proxy_buffer_size(默认 4k/8k),响应体存入proxy_buffers配置的缓冲区。如果响应体数据量超过可用缓冲区总和,剩余数据会写入磁盘临时文件(受proxy_max_temp_file_size限制,默认 1024m)。
缓冲区满或写入临时文件时,Nginx 不会立即返回 502,但如果 upstream 发送速度过快且 buffer 机制未启用(proxy_buffering off),则可能出现数据积压导致超时。
# 缓冲区配置详解 proxy_buffering on; # 启用缓冲区(默认开启) proxy_buffer_size 128k; # 响应头缓冲区,通常 128k 足够 proxy_buffers 8 256k; # 8 个 256k 缓冲区,用于响应体 proxy_busy_buffer_size 256k; # 忙碌时使用的缓冲区大小 proxy_max_temp_file_size 1024m; # 临时文件总大小上限 proxy_temp_file_write_size 128k; # 每次写入临时文件的大小
连接关闭与 keepalive
HTTP/1.1 默认使用 keepalive 保持连接复用。Nginx 通过keepalive_timeout控制连接保持打开的空闲时间,通过keepalive_requests限制单个连接处理的最大请求数。proxy 模块通过proxy_http_version 1.1和proxy_set_header Connection ""来管理与 upstream 的 keepalive 连接。
# upstream keepalive 配置
upstream backend {
server 127.0.0.1:8080;
keepalive 32; # 保持 32 个空闲连接
keepalive_requests 1000;
keepalive_timeout 60s;
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # 必须使用 1.1 才能支持 keepalive
proxy_set_header Connection""; # 清除 Connection 头
}
2.2 upstream 健康检查机制
Nginx upstream 模块支持被动健康检查,通过 max_fails 和 fail_timeout 参数实现故障节点的自动剔除。对于需要主动探测的场景,可以使用第三方模块 nginx_upstream_check_module。
被动健康检查
Nginx 默认对每个 upstream server 进行被动健康检查。当某个 server 连续 max_fails 次连接失败(Connection refused、timeout 等),Nginx 会将该 server 标记为不可用,持续 fail_timeout 秒后重新尝试。
# upstream 被动健康检查配置
upstream backend {
server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;
server 127.0.0.1:8081 max_fails=3 fail_timeout=10s;
server 127.0.0.1:8082 max_fails=3 fail_timeout=10s backup;
}
参数说明:
max_fails:默认 1。设置为 0 则禁用对该 server 的健康检查。
fail_timeout:默认 10s。既是 server 被判定为不可用后持续的时间,也是统计失败次数的时间窗口。
backup:标记为备用服务器,仅在主服务器全部不可用时才启用。
# 查看 upstream 配置和状态
$ nginx -T 2>&1 | grep -A 20"upstream backend"
upstream backend {
server 127.0.0.1:8080 weight=5 max_fails=3 fail_timeout=10s;
server 127.0.0.1:8081 weight=3 max_fails=3 fail_timeout=10s;
server 127.0.0.1:8082 backup;
}
# 观察 error.log 中标记 server 不可用的日志
2026/04/24 0812 [warn] 12487#12487: *2341 upstream server temporarily disabled while connecting to upstream, client: 203.0.113.45, server: api.example.com, request: "GET /api/health HTTP/1.1", upstream: "http://127.0.0.1:8080/api/health"
主动健康检查(nginx_upstream_check_module)
官方 Nginx 不包含主动健康检查模块,需要使用阿里巴巴开源的 nginx_upstream_check_module。该模块允许 Nginx 主动向后端发送 TCP/HTTP 心跳包,根据响应判断 server 是否健康。
# 编译安装时加载模块
./configure --add-module=/path/to/nginx_upstream_check_module
# upstream 配置中添加主动健康检查
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
# 每隔 3 秒对所有 server 进行检查
# 检查类型为 HTTP,检查 URI /health
# 超时 1 秒,连续失败 2 次则标记为不可用
check interval=3000 rise=2 fall=2 timeout=1000type=http;
check_http_send"HEAD /health HTTP/1.0
";
check_http_expect_alive http_2xx http_3xx;
}
# 状态页面查看健康检查结果
location /upstream_status {
check_status;
access_log off;
allow 127.0.0.1;
deny all;
}
$ curl http://127.0.0.1/upstream_status Upstream server status: backend server 127.0.0.1:8080 up 1234567890 ms server 127.0.0.1:8081 down 0 ms (2/2)
动态 upstream 与服务发现
在容器化和微服务架构中,upstream 列表需要动态更新。常见方案:
Consul/Etcd + Consul-template 或 etcd dynamical:通过服务发现系统维护后端列表,使用模板工具动态生成 Nginx 配置文件并 reload。
OpenResty + lua-upstream-nginx-module:使用 Lua 脚本直接操作 Nginx upstream 内存结构,实现运行时更新。
Nginx Plus 商业版:原生支持 DNS 发现和 API 动态修改 upstream。
# consul-template 配置文件示例
upstream backend {
least_conn;
{{ range service"backend"}}
server {{ .Address }}:{{ .Port }};
{{ end }}
}
2.3 缓冲区机制深度解析
缓冲区是 Nginx 与 upstream 之间数据传输的核心机制。理解缓冲区的工作方式,能够解释许多看似莫名其妙的超时和 502 错误。
proxy_buffer_size:响应头缓冲区
Nginx 收到 upstream 的响应状态行和响应头后,将其存储在proxy_buffer_size指定的内存区域中。默认值为 4k 或 8k(取决于操作系统页大小)。对于绝大多数 HTTP 响应头,这个大小足够。如果 upstream 返回的响应头过大(如大量 Cookie、复杂 CSP 头),可能需要增大此值。
# 响应头过大时的错误日志 2026/04/24 0933 [warn] 12487#12487: *4521 could not build large header, shoulder increase the size of proxy_buffer_size
# 调整响应头缓冲区 proxy_buffer_size 256k; # 增大到 256k
proxy_buffers:响应体缓冲区
响应体数据块被缓存在proxy_buffers分配的内存缓冲区中。如果响应体总大小超过所有缓冲区的总容量,剩余数据会写入磁盘临时文件。临时文件路径由proxy_temp_path指定(默认 /var/lib/nginx/proxy)。
# 默认配置 proxy_buffers 8 4k/8k; # 8 个缓冲区,每个 4k 或 8k # 大文件下载场景 proxy_buffers 16 32k; proxy_buffering on; # 确保启用 proxy_max_temp_file_size 2048m; # 允许最大 2G 临时文件
# 出现大量临时文件时的告警日志 2026/04/24 1015 [warn] 12487#12487: *7823 upstream sent chunked body with size larger than the whole proxy buffers space while reading upstream
proxy_busy_buffer_size:忙碌缓冲区
当 Nginx 开始向客户端发送响应时,如果客户端网络速度较慢,Nginx 会继续从 upstream 读取数据并存入缓冲区。proxy_busy_buffer_size定义了在等待客户端接收数据时可以使用的最大缓冲区总大小。默认等于proxy_buffer_size或proxy_buffers中的单个缓冲区大小。
fastcgi_buffering / uwsgi_buffering / scgi_buffering
对于 PHP-FPM 环境,使用 fastcgi_pass 时,缓冲区配置指令前缀变为fastcgi_buffer_。原理与 proxy buffering 完全一致。
# PHP-FPM 环境缓冲区配置 fastcgi_buffering on; fastcgi_buffer_size 128k; fastcgi_buffers 4 256k; fastcgi_busy_buffers_size 256k; fastcgi_max_temp_file_size 1024m; fastcgi_temp_file_write_size 128k;
2.4 超时体系全面解析
Nginx 的超时配置构成一个多层次的体系,不同的超时指令控制请求处理的不同阶段。错误地设置超时是导致 502 和 504 的最常见原因。
proxy_connect_timeout:建立连接的超时
Nginx 等待与 upstream 建立 TCP 握手完成的超时。如果 upstream 服务器的端口未监听或网络不通,这个超时会在第一次连接尝试时触发。默认值 60s 通常足够,但如果 upstream 位于跨地域网络或高负载环境中,可能需要适当调大。
# 默认 60 秒,跨地域场景可调大 proxy_connect_timeout 60s; # 高延迟网络环境 proxy_connect_timeout 10s; # 10 秒足够检测出不可达
# connect timeout 的错误日志 2026/04/24 1108 [error] 12487#12487: *8921 connect() failed (110: Connection timed out) while connecting to upstream, client: 203.0.113.45, upstream: "http://10.0.1.55:8080/"
proxy_send_timeout:向后端发送请求的超时
Nginx 向 upstream 发送请求体的超时。对于 POST/PUT 请求(表单提交、文件上传、API 调用),如果请求体数据量大或上游处理慢,可能触发此超时。默认值 60s。
# 文件上传等大请求场景 proxy_send_timeout 300s; # 5 分钟 client_max_body_size 100m; # 允许最大请求体 100M
proxy_read_timeout:读取后端响应的超时
Nginx 从 upstream 读取响应头和响应体的总超时。这是生产环境中最常需要调整的参数。默认值 60s,对于复杂业务逻辑、数据库查询、第三方 API 调用等场景,60s 往往不够。
# 长时间处理的后端服务 proxy_read_timeout 300s; # 5 分钟 proxy_send_timeout 300s; # 实时性要求高的 API proxy_read_timeout 30s;
# read timeout 的错误日志 2026/04/24 1245 [error] 12487#12487: *10234 upstream timed out (110: Connection timed out) while reading response header of upstream, client: 198.51.100.23, upstream: "http://127.0.0.1:8080/api/report"
send_timeout:发送给客户端的超时
控制 Nginx 向客户端发送数据的超时,与 upstream 无关。如果客户端网络慢或客户端停滞,Nginx 在send_timeout期间未发送任何数据后会关闭连接。这个超时不影响 502,但可能导致客户端收到不完整的响应。
# 默认 60 秒 send_timeout 60s;
keepalive_timeout:keepalive 空呆时间
HTTP/1.1 keepalive 连接在空闲状态下保持的最大时间。超过此时间未收到任何数据,Nginx 会主动关闭连接。
# keepalive 超时设置 keepalive_timeout 75s; # 默认 75 秒 keepalive_requests 1000; # 单连接最大请求数
超时时间的实际计算逻辑
多个超时参数可能同时生效。Nginx 在各个阶段分别计时:
TCP 连接建立:proxy_connect_timeout
请求体发送:proxy_send_timeout(从连接建立到请求体发送完成)
等待响应头:proxy_read_timeout(从请求发送完成到收到响应头)
读取响应体:proxy_read_timeout 继续累积(从收到响应头到响应体读取完成)
三、实战步骤与常见排障路径
3.1 502 问题排查的黄金法则
502 错误的排查需要遵循系统化的步骤,从表象到根因逐层递进。下列步骤经过大量生产环境验证,可以快速定位绝大多数 502 问题。
第一步:确认是否真的是 502
使用 curl -v 查看详细的 HTTP 交互过程,观察 502 是在哪个阶段发生的。
$ curl -v http://api.example.com/api/v2/users 2>&1
* About to connect() to api.example.com port 80 (#0)
* Trying 203.0.113.10...
* Connected to api.example.com (203.0.113.10) port 80 (#0)
> GET /api/v2/users HTTP/1.1
> User-Agent: curl/8.4.0
> Host: api.example.com
> Accept: */*
>
< HTTP/1.1 502 Bad Gateway
< Server: nginx/1.26.2
< Date: Fri, 24 Apr 2026 0812 GMT
< Content-Type: text/html
< Content-Length: 157
< Connection: keep-alive
<
{ [data not shown]
* Connection #0 to host api.example.com left intact
* Closure expected
从 curl 输出可以确认:TCP 连接成功建立(Connected),Nginx 返回了 502 状态码,Server 头显示 Nginx 版本为 1.26.2。这说明问题发生在 Nginx 与 upstream 之间。
第二步:检查 Nginx 错误日志
Nginx error.log 是定位 502 根因的第一手资料。配置error_log级别为 info 或 debug 可以获取更详细的调试信息,但需要注意日志量激增对磁盘和性能的影响。
# 实时跟踪 error.log 中的 502 相关错误
$ tail -f /var/log/nginx/error.log | grep -E"(502|upstream|connect|timed out)"
# 统计最近 1000 条日志中各类错误的数量
$ awk'/[error]/ {count++} /502/ {502count++} /timed out/ {timeout++} /Connection refused/ {refused++} END {print "Total errors:", count, "502 errors:", 502count, "Timeouts:", timeout, "Connection refused:", refused}'/var/log/nginx/error.log
Total errors: 234 502 errors: 45 Timeouts: 12 Connection refused: 33
# 查看错误上下文(前后 5 行)
$ grep -C 5"502"/var/log/nginx/error.log | head -100
# 统计 502 错误的来源 IP
$ awk'/502/ && /client: / {gsub(/,.*/, "", $6); print $6}'/var/log/nginx/error.log | sort | uniq -c | sort -rn
23 203.0.113.45
15 198.51.100.123
7 192.0.2.89
# error.log 配置建议 error_log /var/log/nginx/error.log warn; # 生产环境使用 warn 级别,调试时使用 info # 调试时临时开启 debug(仅限测试环境) error_log /var/log/nginx/error.log debug; # debug 日志量极大,务必在完成调试后恢复
第三步:检查后端服务状态
后端服务未启动或异常是最常见的 502 根因。检查后端进程是否存活、端口/socket 是否监听、进程资源使用情况。
# 检查 PHP-FPM 进程状态
$ systemctl status php-fpm
● php-fpm.service - PHP FastCGI Process Manager
Loaded: loaded (/usr/lib/systemd/system/php-fpm.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2026-04-24 0600 CST; 2h 15min ago
Main PID: 4521 (php-fpm)
Status:"祥 system is running..."
Tasks: 12 (limit: 32768)
Memory: 245.6M
CGroup: /system.slice/php-fpm.service
├─4521 php-fpm: master process (/etc/php-fpm.conf)
├─4522 php-fpm: pool www
├─4523 php-fpm: pool www
└─4524 php-fpm: pool www
# 检查监听端口
$ ss -tlnp | grep -E':(80|443|8080|9000|php)'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 127.0.0.1:9000 0.0.0.0:* users:(("php-fpm",pid=4522,fd=9),("php-fpm",pid=4523,fd=9))
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=3421,fd=18))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=12487,fd=6),("nginx",pid=12488,fd=6))
# 如果使用 Unix socket
$ ls -la /var/run/php-fpm/
total 8
srwxr-xr-x 3 root www-data www-data 120 Apr 24 06:00 /var/run/php-fpm/
srwxrwxrwx 1 www-data www-data 6 Apr 24 06:00 www.sock
# 测试 socket 连接性(使用 php-fpm 官方探活接口)
$ SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET cgi-fcgi -bind-connect /var/run/php-fpm/www.sock
Content-type: text/plain
X-Powered-By: PHP 8.3.8
Status: 200 OK
X-Request-Id: abc123
Free: 1
MPool: enabled
Prober:builtin
Content-Length: 6
OK
# 检查后端进程 CPU 和内存占用
$ top -b -n 1 | head -20
top - 0833 up 42 days, 3:22, 2 users, load average: 3.45, 2.89, 2.76
Tasks: 287 total, 5 running, 282 sleeping, 0 stopped, 0 zombie
%Cpu(s): 78.5 us, 12.3 sy, 0.0 ni, 9.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 32768.0 total, 4523.4 free, 18234.5 used, 10010.1 buff/cache
MiB Swap: 8192.0 total, 8192.0 free, 0.0 used. 11234.3 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4522 www-data 20 0 523456 28934 9212 S 45.2 0.1 234:56 php-fpm
4523 www-data 20 0 512234 24567 8734 S 38.7 0.1 198:34 php-fpm
3421 root 20 0 1245678 456123 23456 S 12.3 1.4 56:78 node
第四步:确认网络连通性
排除物理网络层面的问题。即使 upstream 在本机,也需要验证端口可连接性。
# telnet 测试端口连通性(交互式命令,Ctrl+] 退出) $ telnet 127.0.0.1 8080 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is'^]'. # nc 测试端口连通性(更现代的方式) $ nc -zv 127.0.0.1 8080 Connection to 127.0.0.1 8080 port [tcp/*] succeeded! $ nc -zv 127.0.0.1 9000 -w 3 Connection to 127.0.0.1 9000 port [tcp/*] succeeded! # 如果是域名形式的 upstream,测试 DNS 解析 $ dig +short api-backend.internal.example.com 10.0.1.55 10.0.1.56 10.0.1.57 $ nslookup api-backend.internal.example.com # 测试 TCP 握手时间(使用 httping) $ httping -g http://127.0.0.1:8080/health -c 3 PING 127.0.0.1:8080 (to=127.0.0.1): connected to 127.0.0.1:8080 (301 bytes), seq=0 time=00.35 ms connected to 127.0.0.1:8080 (301 bytes), seq=1 time=00.31 ms connected to 127.0.0.1:8080 (301 bytes), seq=2 time=00.28 ms --- http://127.0.0.1:8080/ ping statistics --- 3 connects, 3 ok, 0.00% roundtrip loss, 0.0/3.0/0.3/0.4 ms min/avg/max # iptables/firewalld 规则检查 $ iptables -L -n | grep -E'(DROP|REJECT)'| head -20 $ firewall-cmd --list-all 2>/dev/null
第五步:验证 upstream 配置是否正确
检查 nginx.conf 中 upstream 块的配置,确保 IP 地址、端口、权重、备份服务器等参数正确。
# 测试 Nginx 配置语法 $ nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conftestis successful # 完整输出配置树(包含所有 include 文件) $ nginx -T 2>&1 | head -500 # 检查特定 upstream 块的配置 $ nginx -T 2>&1 | grep -A 10"^ upstream backend"
3.2 日志深度解读
Nginx error.log 中记录的错误信息是诊断 502 问题最直接的证据。不同的错误信息对应不同的根因和处理方式。
"upstream prematurely closed connection"
表示 Nginx 与 upstream 之间的连接在 Nginx 读取完响应头之前就被 upstream 主动关闭。这通常意味着 upstream 进程崩溃、OOM 被杀、或遭遇了致命错误。
# 典型日志 2026/04/24 0832 [error] 12487#12487: *8921 upstream prematurely closed connection while reading response header of upstream, client: 203.0.113.45, server: api.example.com, request: "GET /api/v2/users HTTP/1.1", upstream: "http://127.0.0.1:8080/api/v2/users", upstream_connection: "9612", upstream_bytes_sent: 234, upstream_bytes_received: 0
关键字段解读:
upstream prematurely closed connection:根因,upstream 提前关闭了连接
upstream_connection: "9612":Nginx 侧连接的标识符,可用于关联同一连接的多次请求
upstream_bytes_sent: 234:已发送给 upstream 的字节数
upstream_bytes_received: 0:从 upstream 收到的字节数,说明 upstream 尚未返回任何数据就被关闭了
排查步骤:
检查 upstream 服务(后端应用)的错误日志,寻找崩溃或 panic 的记录
检查系统日志(dmesg、/var/log/messages)是否有关于 OOM Killer 的记录
确认后端服务是否因为代码 bug、资源耗尽而崩溃
# 检查 dmesg 中的 OOM Killer 记录 $ dmesg -T | grep -i"oom|killed process"| tail -20 [Thu Apr 24 0800 2026] php-fpm[4522]: oom_reaper: reaped process 4522 (php-fpm), now anon-rss: 0kb, file-rss: 0kb # 检查 /var/log/messages 中的相关记录 $ grep -i"oom|kill|php-fpm"/var/log/messages | tail -30
"connect() failed (111: Connection refused)"
Nginx 尝试连接 upstream 时,目标端口无进程监听。这是配置错误或后端服务未启动的明确信号。
# 典型日志 2026/04/24 0918 [error] 12487#12487: *9234 connect() failed (111: Connection refused) while connecting to upstream, client: 198.51.100.23, server: www.example.com, request: "POST /api/orders HTTP/1.1", upstream: "http://127.0.0.1:9000/api/orders", upstream_connection: "128"
关键字段解读:
connect() failed:连接建立失败
(111: Connection refused):系统级 errno,111 = ECONNREFUSED,表示目标地址有响应但端口未监听(对比 ENETUNREACH = Network unreachable)
排查步骤:
确认 upstream 服务进程是否运行:systemctl status php-fpm或ss -tlnp
确认 upstream 配置的 IP:端口是否正确
如果是 Unix socket,确认 socket 文件是否存在且权限正确
# 模拟 Nginx 的连接行为 $ strace -e trace=connect -s 200 nginx -s reload 2>&1 | grep -i"connection refused" # 或者对已知的后端端口抓包 $ tcpdump -i lo port 9000 -nn 2>&1
"no live upstreams while connecting"
所有 upstream server 都不可用,Nginx 找不到可用的后端节点。
# 典型日志 2026/04/24 1007 [error] 12487#12487: *10234 no live upstreams while connecting to upstream, client: 203.0.113.89, server: api.example.com, request: "GET /api/health HTTP/1.1"
排查步骤:
检查 upstream 块中所有 server 的健康状态
可能是所有 server 都触发了 max_fails 阈值被暂时禁用
检查 Nginx 配置中是否有语法错误导致 upstream 块无效
# 检查 upstream 配置 $ nginx -T 2>&1 | grep -A 15"upstream backend" # 检查所有 upstream server 是否都被标记为 down # 在 error.log 中搜索 upstream server 状态变化 $ grep"upstream.*server.*down|upstream.*server.*disabled"/var/log/nginx/error.log | tail -20 2026/04/24 0812 [warn] 12487#12487: *234 upstream server 127.0.0.1:8080 is marked as down 2026/04/24 0815 [warn] 12487#12487: *235 upstream server 127.0.0.1:8081 is marked as down
"upstream timed out (110: Connection timed out)"
与 upstream 的连接建立成功,但在 proxy_read_timeout 规定的时间内未收到响应,Nginx 主动关闭连接。
# 典型日志 - 响应头超时 2026/04/24 1133 [error] 12487#12487: *10891 upstream timed out (110: Connection timed out) after 30000 ms (110: Connection timed out) while connecting to upstream, client: 198.51.100.67, server: www.example.com, request: "POST /api/upload HTTP/1.1", upstream: "http://127.0.0.1:9090/api/upload" # 典型日志 - 读取响应超时 2026/04/24 1244 [error] 12487#12487: *11567 upstream timed out (110: Connection timed out) while reading response header of upstream, client: 203.0.113.112, server: api.example.com, request: "GET /api/reports HTTP/1.1", upstream: "http://127.0.0.1:8080/api/reports", upstream_connection: "15623"
排查步骤:
检查后端服务的处理时间(数据库查询、文件 I/O、第三方 API 调用)
确认 proxy_read_timeout 设置是否合理
使用后端应用自身的慢查询日志定位瓶颈
# PHP-FPM 慢查询日志 $ grep -i"slow"/var/log/php-fpm/www-slow.log | tail -20 [24-Apr-2026 1112] [pool www] pid 5534 script: /var/www/html/api/report.php call: mysqli_query time: 45.234567 s memory: 128.00M # Node.js 慢日志(使用 --inspect 配合 APM 工具) $ journalctl -u node-backend --since"2026-04-24 1100"| grep -i"slow|timeout"
access.log 的 502 占比统计
通过分析 access.log 中各类状态码的分布,可以量化 502 错误的严重程度和变化趋势。
# 统计 access.log 中各状态码的数量
$ awk'{code[$9]++} END {for (c in code) print c, code[c]}'/var/log/nginx/access.log | sort -k2 -rn
200 1289345
502 4523
499 1234
304 890
404 567
500 234
503 89
# 计算 502 错误率
$ total=$(wc -l < /var/log/nginx/access.log); errors502=$(grep -c " 502 " /var/log/nginx/access.log); echo"scale=4; $errors502 / $total * 100" | bc
0.3489%
# 按时间段统计 502 错误
$ awk '$4 ~ /[24/Apr/2026:1[0-2]/ && $9 == 502 {hour[$4]++} END {for (h in hour) print h, hour[h]}' /var/log/nginx/access.log
[24/Apr/202600:00 +0800] 23
[24/Apr/202600:00 +0800] 45
[24/Apr/202600:00 +0800] 12
# 统计产生 502 错误最多的请求路径
$ awk '$9 == 502 {path[$7]++} END {for (p in path) print path[p], p}' /var/log/nginx/access.log | sort -rn | head -10
234 /api/v2/users
189 /api/orders/submit
123 /api/reports/generate
67 /api/payments/callback
日志级别配置与影响
Nginx 日志级别从低到高:debug、info、notice、warn、error、crit、alert、emerg。生产环境建议使用 warn 或 error 级别,避免 debug 级别产生过大的日志量。
# 日志级别说明 debug: 调试信息,仅在编译时 --with-debug 启用 info: 一般信息 notice: 需要注意的信息 warn: 警告信息,可能存在问题但不影响运行 error: 错误信息 crit: 严重错误,部分功能不可用 alert: 必须立即处理 emerg: 系统不可用
# 配置示例
error_log /var/log/nginx/error.log warn;
# 临时开启 debug(测试环境)
error_log /var/log/nginx/error.log debug;
# debug 级别的连接追踪
events {
debug_connection 203.0.113.0/24; # 仅对特定 IP 开启 debug
debug_connection 127.0.0.1;
}
分布式 trace:request_id 串联日志
在多层代理架构中,单个请求可能经过多个 Nginx 节点。使用 request_id 将各层日志串联起来,可以实现端到端的请求追踪。
# Nginx 自动生成 $request_id(1.11.0+)
# 格式:16 字节的十六进制随机数
# 在请求头中传递 request_id
location / {
proxy_pass http://backend;
proxy_set_header X-Request-ID$request_id;
}
# 在 PHP 中获取 request_id
$requestId=$_SERVER['HTTP_X_REQUEST_ID'] ??'';
# 在 Node.js 中获取 request_id
const requestId = req.headers['x-request-id'];
# 查询串联后的日志
$ grep"X-Request-Id: abc12345"/var/log/nginx/error.log
2026/04/24 0832 [error] 12487#12487: *8921 upstream prematurely closed connection..., request_id: "abc12345"
2026/04/24 0833 [error] 4522#4522: *8921 php-fpm: pool www: child exited..., request_id: "abc12345"
$ grep"abc12345"/var/log/php-fpm/www-error.log
[24-Apr-2026 0832 UTC] PHP Fatal error: Allowed memory size of 128M exhaustedin/var/www/html/api/v2/users.php on line 234, request_id: abc12345
3.3 常见场景的排障路径
场景一:PHP-FPM 进程全挂
PHP-FPM 是最常见的后端服务之一,PHP-FPM 进程异常退出是导致 502 的高频原因。
症状识别:
error.log 大量"connect() failed (111: Connection refused) while connecting to upstream"
upstream 指向 php-fpm socket 或端口
curl 测试直接连接后端也失败
排查步骤:
# 1. 检查 PHP-FPM 服务状态 $ systemctl status php-fpm ● php-fpm.service - PHP FastCGI Process Manager Loaded: loaded (/usr/lib/systemd/system/php-fpm.service; enabled; vendor preset: enabled) Active: inactive (dead) since Thu 2026-04-24 0812 CST; 2h 15min ago Main PID: 4521 (code=exited, status=0/SUCCESS) # 2. 检查 PHP-FPM 主进程退出原因 $ journalctl -u php-fpm --since"2026-04-24 0800"--until"2026-04-24 0800" Apr 24 0812 web01 systemd[1]: php-fpm.service: Main process exited, code=killed, status=9/KILL Apr 24 0812 web01 systemd[1]: php-fpm.service: Failed with result'signal'. # 3. 检查 dmesg 中是否有 OOM Killer 记录 $ dmesg -T | grep -E"php|oom|killed"| tail -10 [Thu Apr 24 0811 2026] php-fpm[4522]: memory usage 256MB exceedslimit256MB, terminated [Thu Apr 24 0811 2026] Out of memory: Killed process 4522 total-vm:512MB, anon-rss:256MB file-rss:0KB # 4. 检查 PHP-FPM 配置中的进程管理参数 $ grep -E"^pm|^proces|child|memory"/etc/php-fpm/www.conf pm = dynamic pm.max_children = 50 pm.start_servers = 10 pm.min_spare_servers = 5 pm.max_spare_servers = 20 pm.max_requests = 500 ;emergency_restart_threshold = 10 ;emergency_restart_interval = 1m ;process_control_timeout = 10s php_admin_value[memory_limit] = 256M # 5. 检查 PHP-FPM 错误日志 $ cat /var/log/php-fpm/www-error.log | tail -30 [24-Apr-2026 0810 UTC] PHP Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 16384 bytes)in/var/www/html/api/v2/users.php on line 145 [24-Apr-2026 0811 UTC] PHP Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 32768 bytes)in/var/www/html/api/v2/users.php on line 234
解决方案:
临时恢复服务:重启 PHP-FPM
$ systemctl restart php-fpm $ systemctl status php-fpm ● php-fpm.service - PHP FastCGI Process Manager Loaded: loaded (/usr/lib/systemd/system/php-fpm.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2026-04-24 1000 CST; 5s ago
长期优化:根据 OOM 日志定位内存消耗最大的 PHP 脚本,优化代码或上调 memory_limit
# 临时调高 memory_limit(不推荐作为长期方案) $ sed -i's/php_admin_value[memory_limit] = 256M/php_admin_value[memory_limit] = 512M/'/etc/php-fpm/www.conf $ systemctl restart php-fpm # 优化 pm.max_children(根据可用内存计算) # 公式:max_children = (总内存 - 操作系统预留 - 其他服务内存) / 单个 PHP 进程内存 # 假设服务器 32G 内存,OS 预留 4G,MySQL 占用 8G,单个 PHP 进程平均 64M # max_children = (32 - 4 - 8) * 1024 / 64 = 320
根因预防:
配置 PHP-FPM emergency_restart_threshold,当进程在规定时间内异常退出次数超过阈值时自动重启
配置 OOM Score Adj,使 PHP-FPM 进程更容易被 OOM Killer 先选中
使用 PHP 内存分析工具(xdebug、memprof)定位内存泄漏
场景二:Node.js/Python 后端 CPU 跑满
后端服务 CPU 跑满时,请求处理速度急剧下降,Nginx 等待后端响应超时,最终导致 502。
症状识别:
error.log 出现"upstream timed out"
502 错误持续出现,但后端进程仍在运行
系统 load average 显著升高
排查步骤:
# 1. 检查 CPU 使用情况 $ top -b -n 1 -p $(pgrep -d','node) top - 0815 up 42 days, 3:37, 2 users, load average: 8.45, 6.89, 5.76 Tasks: 12 total, 9 running, 3 sleeping, 0 stopped, 0 zombie %Cpu(s): 95.5 us, 3.2 sy, 0.0 ni, 1.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3421 root 20 0 1245678 456123 23456 R 78.5 1.4 2345:56 node 3422 root 20 0 234567 123456 8901 R 15.2 0.4 456:34 node 3423 root 20 0 212345 112234 7802 R 1.2 0.3 123:45 node # 2. 检查 Node.js 进程的请求队列长度 $ curl -s http://127.0.0.1:8080/metrics | grep -E"nodejs_active_requests|eventloop_lag" # HELP nodejs_active_requests Number of active requests # TYPE nodejs_active_requests gauge nodejs_active_requests 150 # HELP nodejs_eventloop_lag Lag in the Node.js event loop in milliseconds # TYPE nodejs_eventloop_lag gauge nodejs_eventloop_lag 5234 # 3. 分析 Node.js 慢日志(如果有) $ cat /var/log/node-backend/slow.log | tail -20 [SLOW] 2026-04-24T0812.234Z - 5034ms - /api/v2/report - Database query slow [SLOW] 2026-04-24T0815.567Z - 6123ms - /api/v2/report - Memory allocation high # 4. Python (Gunicorn) 场景:检查 worker 状态 $ ps aux | grep gunicorn | grep -v grep root 5621 0.0 2.1 245678 456789 ? S Apr23 0:30 gunicorn: master [app] www-data 5622 95.0 3.2 267890 567890 ? R Apr24 234:56 gunicorn: worker [/var/www/app] www-data 5623 92.0 3.1 259876 543210 ? R Apr24 198:34 gunicorn: worker [/var/www/app] $ curl http://127.0.0.1:8000/syncdict # Gunicorn 同步模式下单个请求阻塞整个 worker
解决方案:
快速止血:增加 worker 进程数量或切换到异步模式
# Node.js: 临时增加 cluster worker 数量 # 编辑 app.js,将 worker 数量从 4 增加到 8 $ sed -i's/workers: 4/workers: 8/'/etc/node-backend/config.js $ systemctl restart node-backend # Python: 增加 Gunicorn worker 数量,使用异步 worker $ sed -i's/workers=4/workers=8/'/etc/gunicorn/config.py $ sed -i's/worker-class=sync/worker-class=uvicorn.workers.UvicornWorker/'/etc/gunicorn/config.py $ systemctl restart gunicorn
定位瓶颈:使用 profiler 找到 CPU 热点代码
# Node.js: 使用 --prof 生成 V8 profiling 数据 $kill-USR2 $(pgrep node) # 触发 profiling $ sleep 30 $kill-USR2 $(pgrep node) # 停止 profiling $ node --prof-process isolate-*.log| head -30
优化超时配置:根据后端处理能力调整 Nginx 超时
# upstream 块中添加合理的超时配置
upstream backend {
server 127.0.0.1:8080;
keepalive 16;
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
# 根据后端处理能力调整超时
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
}
场景三:数据库连接池耗尽
后端服务依赖数据库,当数据库连接池耗尽时,后端请求在等待数据库连接时阻塞,间接导致 Nginx 侧超时。
症状识别:
502 错误呈现周期性(如每 30 分钟出现一次高峰)
后端应用日志中大量"connection timeout"或"pool exhausted"
数据库服务器 CPU/内存正常,但连接数达到上限
排查步骤:
# 1. 检查 MySQL 连接数 $ mysql -e"SHOW PROCESSLIST;"| wc -l 145 $ mysql -e"SHOW PROCESSLIST;"| grep -E"Sleep|Query"| head -20 +----+------+-----------+--------------------+---------+------+----------+-----------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+--------------------+---------+------+----------+-----------------------+ | 1 | root | localhost | information_schema | Query | 0 | starting | SHOW PROCESSLIST | | 45 | app | web01:456 | myapp_production | Sleep | 892 | | NULL | | 46 | app | web01:457 | myapp_production | Sleep | 891 | | NULL | | 47 | app | web01:458 | myapp_production | Sleep | 890 | | NULL | | 48 | app | web02:123 | myapp_production | Sleep | 892 | | NULL | +----+------+-----------+--------------------+---------+------+----------+-----------------------+ # 2. 检查 MySQL 最大连接数配置 $ mysql -e"SHOW VARIABLES LIKE 'max_connections';" +-----------------+-------+ | Variable_name | Value | +-----------------+-------+ | max_connections | 151 | +-----------------+-------+ # 3. 检查 PHP/Python 应用的数据库连接池配置 $ grep -E"max_connections|pool_size|connection_pool"/var/www/html/config/database.php $connPool= new PDO($dsn,$user,$pass, [ PDO::ATTR_POOL =>true, PDO::ATTR_POOL_SIZE => 20, ]); # 4. 检查慢查询日志(长时间运行的 Query 会占用连接) $ mysql -e"SHOW GLOBAL STATUS LIKE 'Slow_queries';" +---------------+-------+ | Variable_name | Value | +---------------+-------+ | Slow_queries | 1234 | +---------------+-------+ $ cat /var/log/mysql/slow-query.log | head -30
解决方案:
数据库端优化:增加 max_connections,调优 wait_timeout
-- 临时增加连接数上限 SETGLOBALmax_connections =500; -- 检查并调优 wait_timeout(避免 Sleep 连接长期占用) SHOWGLOBALVARIABLESLIKE'wait_timeout'; SETGLOBALwait_timeout =600; SETGLOBALinteractive_timeout =600;
应用端优化:使用连接池管理,设置连接超时,快速失败
// PHP: 使用 Doctrine DBAL 连接池,设置短超时
$config =newDoctrineDBALConfiguration();
$config->setDriverChain(['adapter'=>'pdo_mysql']);
$connection = DoctrineDBALManagerRegistry::getConnection([
'driver'=>'pdo_mysql',
'host'=>'10.0.1.100',
'dbname'=>'myapp',
'user'=>'app',
'password'=>'xxx',
'options'=> [
PDO::ATTR_TIMEOUT =>5, // 5 秒超时
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
],
]);
Nginx 端配置:使用短超时和快速失败,避免请求堆积
location / {
proxy_pass http://backend;
proxy_connect_timeout 3s;
proxy_send_timeout 10s;
proxy_read_timeout 30s;
# 后端处理过慢时立即返回 502,不累积请求
proxy_next_upstream error timeout http_502;
}
场景四:Socket 文件权限问题
PHP-FPM 使用 Unix Domain Socket 与 Nginx 通信时,socket 文件的权限和所有权必须正确配置,否则 Nginx 无法连接 PHP-FPM。
症状识别:
error.log 中出现"connect() to unix:/var/run/php-fpm/www.sock failed (13: Permission denied)"
PHP-FPM 进程正常运行,但 Nginx 返回 502
直接测试 socket 连通性失败
排查步骤:
# 1. 检查 socket 文件权限
$ ls -la /var/run/php-fpm/
total 8
srwxr-xr-x 3 root www-data www-data 120 Apr 24 06:00 /var/run/php-fpm/
srwxrwxrwx 1 www-data www-data 6 Apr 24 06:00 www.sock
# 2. 检查 Nginx worker 进程的用户
$ ps aux | grep"nginx: worker"| head -3
root 12487 0.0 0.3 56789 23456 ? S Apr23 0:12 nginx: worker process
root 12488 0.0 0.3 56790 23457 ? S Apr23 0:11 nginx: worker process
# nginx.conf 中 user 指令
$ grep"^user"/etc/nginx/nginx.conf
user nginx nginx;
# 3. 检查 PHP-FPM pool 配置中的 listen.owner 和 listen.group
$ grep -E"^listen.|listen.owner|listen.group|listen.mode"/etc/php-fpm/www.conf
listen = /var/run/php-fpm/www.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
# 4. 测试 socket 连接性
$ sudo -u nginx php-fpm-test-connection /var/run/php-fpm/www.sock
# 或使用 ss 查看 socket 状态
$ ss -x | grep php-fpm
u_str LISTEN 0 511 /var/run/php-fpm/www.sock 12345 users:(("php-fpm",pid=4522,fd=9))
SELinux/AppArmor 拦截检查:
# 检查 SELinux 状态
$ getenforce
Enforcing
# 查看 SELinux 阻止的访问
$ grep nginx /var/log/audit/audit.log | grep -i"denied|refused"| tail -10
type=AVC msg=audit(1713936000.234 avc: denied { connectto }for pid=12487 comm="nginx"path="/var/run/php-fpm/www.sock"scontext=system_uhttpd_t:s0 tcontext=system_uphp-fpm_t:s0 tclass=unix_stream_socket permissive=0
# 临时解决:设置 SELinux 允许 nginx 连接 php-fpm socket
$ setsebool -P httpd_can_network_connect 1
# 永久解决:添加 SELinux 策略
$ cat > /etc/selinux/local/nginx_php_fpm.te << 'EOF'
module nginx_php_fpm 1.0;
require {
type httpd_t;
type php-fpm_t;
class unix_stream_socket connectto;
}
allow httpd_t php-fpm_t:unix_stream_socket connectto;
EOF
$ checkmodule -M -m -o nginx_php_fpm.mod nginx_php_fpm.te
$ semodule_package -o nginx_php_fpm.pp -m nginx_php_fpm.mod
$ semodule -i nginx_php_fpm.pp
解决方案:
统一用户和组
# 方案一:让 Nginx 和 PHP-FPM 使用相同的用户
# /etc/php-fpm/www.conf
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
# /etc/nginx/nginx.conf
user nginx nginx;
# 方案二:使用 TCP 连接而非 Unix socket
# /etc/php-fpm/www.conf
listen = 127.0.0.1:9000
# /etc/nginx/nginx.conf
location ~ .php$ {
fastcgi_pass 127.0.0.1:9000;
}
修复 socket 权限
$ chown www-data:www-data /var/run/php-fpm $ chmod 0775 /var/run/php-fpm $ systemctl restart php-fpm
场景五:反代到 Docker 容器
Docker 容器的网络模式、容器健康状态、容器重启策略等都会影响 Nginx 对容器的代理行为。
症状识别:
upstream 指向 Docker 容器 IP 或映射端口
502 错误在容器重启后突然出现
docker ps 显示容器状态不稳定
排查步骤:
# 1. 检查容器状态
$ docker ps -a
CONTAINER ID IMAGE STATUS PORTS NAMES
abc123def456 myapp:latest Up 2 hours 0.0.0.0:8080->8080/tcp web01
def456ghi789 myapp:latest Restarting 0.0.0.0:8081->8080/tcp web02
ghi789jkl012 myapp:latest Exited (1) 5m 0.0.0.0:8082->8080/tcp web03
# 2. 检查容器日志
$ docker logs def456ghi789 --tail 50
[2026-04-24 0800] INFO: Starting server on port 8080
[2026-04-24 0801] ERROR: Database connection failed: connection timeout
[2026-04-24 0801] INFO: Retryingin5 seconds...
[2026-04-24 0806] ERROR: Database connection failed: connection timeout
[2026-04-24 0806] FATAL: Could not connect to database after 5 attempts, exiting
# 3. 检查 Docker 网络模式
$ docker inspect def456ghi789 | grep -A 10"NetworkSettings"
"NetworkSettings": {
"Bridge":"",
"SandboxID":"xyz123",
"Gateway":"172.17.0.1",
"IPAddress":"172.17.0.8",
"Ports": {
"8080/tcp": [
{
"HostIp":"0.0.0.0",
"HostPort":"8081"
}
]
}
}
# 4. 测试容器网络连通性
$ dockerexec-it abc123def456 ping -c 3 db.internal.example.com
PING db.internal.example.com (10.0.1.100) 56(84) bytes of data.
64 bytes from db.internal.example.com (10.0.1.100): icmp_seq=1 ttl=63 time=0.5 ms
$ dockerexec-it abc123def456 curl -v http://localhost:8080/health
* Connected to localhost:8080 (127.0.0.1) port 8080
< HTTP/1.1 200 OK
# 5. 检查 Docker daemon 日志
$ journalctl -u docker --since "2026-04-24 0800" | tail -50
解决方案:
使用 Docker Compose 健康检查和 depends_on 确保启动顺序
# docker-compose.yml version:'3.8' services: db: image:mysql:8.0 healthcheck: test:["CMD","mysqladmin","ping","-h","localhost"] interval:10s timeout:5s retries:5 app: image:myapp:latest depends_on: db: condition:service_healthy healthcheck: test:["CMD","curl","-f","http://localhost:8080/health"] interval:15s timeout:5s retries:3 restart:on-failure
在 Nginx upstream 中使用容器名称(Docker Compose 网络内)
upstream backend {
server web01:8080;
server web02:8080;
server web03:8080 backup;
}
location / {
proxy_pass http://backend;
resolver 127.0.0.11 valid=10s; # Docker 内置 DNS
proxy_set_header Host $host;
}
配置 Nginx upstream 主动健康检查
upstream backend {
server web01:8080;
server web02:8080;
server web03:8080 backup;
check interval=5000 rise=2 fall=3 timeout=3000 type=http;
check_http_send "HEAD /health HTTP/1.0
";
check_http_expect_alive http_2xx;
}
3.4 配置优化实战
超时参数调优
超时配置是 502 问题的核心配置项。根据业务特点合理设置超时,可以在保护后端和保证可用性之间取得平衡。
# 生产环境推荐的超时配置
proxy_connect_timeout 10s; # 连接超时,短即可
proxy_send_timeout 60s; # 发送请求超时,根据请求体大小调整
proxy_read_timeout 120s; # 读取响应超时,根据业务处理时间调整
# 上传/下载大文件的场景
client_max_body_size 500m; # 允许最大请求体 500M
proxy_read_timeout 600s; # 大文件处理需要更长超时
# API 服务的超时配置
location /api/ {
proxy_pass http://backend_api;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
# 启用请求缓冲,用于大请求体
proxy_request_buffering on;
proxy_buffer_size 256k;
}
# 验证超时配置生效 $ nginx -T 2>&1 | grep -E"proxy_(connect|send|read)_timeout" proxy_connect_timeout 10s; proxy_send_timeout 60s; proxy_read_timeout 120s; # 通过压力测试验证超时行为 $ ab -n 100 -c 10 -s 30 http://api.example.com/api/v2/report # 观察是否会因为超时导致 502
缓冲区优化
缓冲区配置直接影响 Nginx 转发大量数据时的效率。配置不当可能导致内存占用过高或频繁写入临时文件。
# 大流量站点推荐缓冲区配置
proxy_buffering on;
proxy_buffer_size 256k; # 响应头缓冲区
proxy_buffers 16 256k; # 响应体缓冲区,16 * 256k = 4M
proxy_busy_buffer_size 512k; # 忙碌时缓冲区
# 临时文件配置(用于超过内存缓冲区的响应)
proxy_max_temp_file_size 2048m; # 临时文件总大小上限
proxy_temp_file_write_size 256k; # 单次写入大小
# 静态文件代理(减少后端压力)
location /static/ {
proxy_pass http://backend_static;
proxy_buffering on;
proxy_cache_valid 200 60m; # 缓存 200 响应 60 分钟
proxy_cache_valid 404 1m;
}
# 监控缓冲区使用情况 $ watch -n 1'cat /proc/$(pgrep nginx | head -1)/status | grep -E "VmPeak|VmSize|Rss"' Every 1.0s: cat /proc/12487/status | grep -E"VmPeak|VmSize|Rss" VmPeak: 524288 kB VmSize: 498234 kB Rss: 123456 kB # 检查是否有大量临时文件 $ ls -lah /var/lib/nginx/proxy/ | wc -l 23 $ du -sh /var/lib/nginx/proxy/ 1.2G
Keepalive 配置
HTTP keepalive 可以复用 TCP 连接,显著减少连接建立的开销。在高并发场景中,合理配置 upstream keepalive 是保护后端的重要手段。
# upstream keepalive 配置
upstream backend {
server 127.0.0.1:8080 weight=5;
server 127.0.0.1:8081 weight=3;
server 127.0.0.1:8082 backup;
# keepalive 配置
keepalive 32; # 保持 32 个空闲连接
keepalive_requests 1000; # 单连接最大请求数
keepalive_timeout 60s; # 空闲连接超时
}
# location 中启用 HTTP/1.1 和清除 Connection 头
location / {
proxy_pass http://backend;
# 必须使用 HTTP/1.1 才能支持 keepalive
proxy_http_version 1.1;
# 清除 Connection 头,否则 nginx 会保持短连接
proxy_set_header Connection "";
# 可选:添加存活探测
proxy_connect_timeout 10s;
}
# 对比有无 keepalive 的性能差异 # 无 keepalive $ wrk -t4 -c100 -d30s http://127.0.0.1/api/test Requests/sec: 1234.56 Latency avg: 45.67ms # 有 keepalive(32 连接) $ wrk -t4 -c100 -d30s http://127.0.0.1/api/test Requests/sec: 4567.89 Latency avg: 12.34ms # 性能提升约 3.7 倍
3.5 预防性监控
502 错误率告警
#!/bin/bash
# 监控 502 错误率,超过阈值则告警
LOG_FILE="/var/log/nginx/access.log"
THRESHOLD=1.0 # 阈值 1%
calculate_502_rate() {
total=$(wc -l < "$LOG_FILE")
errors502=$(grep -c " 502 ""$LOG_FILE" 2>/dev/null ||echo0)
if["$total"-eq 0 ];then
echo"0"
return
fi
rate=$(echo"scale=4;$errors502/$total* 100"| bc)
echo"$rate"
}
rate=$(calculate_502_rate)
result=$(echo"$rate>$THRESHOLD"| bc)
if["$result"-eq 1 ];then
echo"CRITICAL: 502 error rate is${rate}%, threshold is${THRESHOLD}%"
# 触发告警(对接 Prometheus Alertmanager / Grafana / PagerDuty 等)
curl -X POST"http://alertmanager:9093/api/v1/alerts"
-H"Content-Type: application/json"
-d'[{"labels":{"alertname":"Nginx502High","severity":"critical"}}]'
fi
Prometheus + Grafana 监控配置
# prometheus.yml - scrape 配置
scrape_configs:
-job_name:'nginx'
static_configs:
-targets:['127.0.0.1:9100']# nginx-vts 或 nginx-exporter
# Grafana 查询 - 502 错误率
# Nginx upstream response time (5xx)
sum(rate(nginx_upstream_response_seconds_count{upstream=~".*",status=~"5.."}[5m]))by(upstream)
# Nginx upstream request errors
sum(rate(nginx_upstream_request_errors_total[5m]))by(upstream)
# Nginx connection status
nginx_connections_total{status="active"}
nginx_connections_total{status="reading"}
nginx_connections_total{status="writing"}
nginx_connections_total{status="waiting"}
upstream 响应时间分布
# 使用 awk 分析 access.log 中 upstream 响应时间分布
$ awk -F'"''$2 ~ /upstream/ {
split($2, parts, " ")
for (i=1; i<=length(parts); i++) {
if (parts[i] ~ /upstream_response_time/) {
split(parts[i], kv, ":")
time = kv[3] + 0
if (time < 0.1) bucket["<100ms"]++
else if (time < 0.5) bucket["100-500ms"]++
else if (time < 1) bucket["500ms-1s"]++
else if (time < 5) bucket["1-5s"]++
else if (time < 30) bucket["5-30s"]++
else bucket[">30s"]++
total++
break
}
}
}
END {
for (b in bucket) {
pct = int(bucket[b] / total * 100)
printf "%s: %d (%d%%)
", b, bucket[b], pct
}
}'/var/log/nginx/access.log
<100ms: 12345 (45%)
100-500ms: 9876 (36%)
500ms-1s: 2345 (9%)
1-5s: 1234 (5%)
5-30s: 567 (2%)
>30s: 33 (1%)
四、生产环境最佳实践
4.1 架构优化
多层 Nginx 架构
在大型生产环境中,建议部署两层 Nginx:边缘代理层(Edge Proxy)和应用代理层(Application Proxy)。边缘代理负责 SSL 终结、请求日志、基础安全过滤、静态资源缓存;应用代理负责业务路由、upstream 健康检查、负载均衡、故障转移。
Internet
|
Edge Proxy (NLB)
1.1.1.1 / 1.1.1.2
SSL Termination
Static Cache
Rate Limiting
|
Application Proxy Cluster
10.0.1.10 / 10.0.1.11 / 10.0.1.12
Upstream Health Check
Load Balancing
Business Routing
|
Backend Services
+------------+------------+
| | |
PHP-FPM Node.js Python
| | |
Database Redis PostgreSQL
# 边缘代理层配置 - /etc/nginx/edge.conf
# 处理 SSL 终结和静态资源
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared10m;
ssl_session_timeout 1d;
# 静态资源缓存
location /static/ {
proxy_pass http://cdn_backend;
proxy_cache static_cache;
proxy_cache_valid 200 7d;
add_header X-Cache-Status $upstream_cache_status;
}
# 动态请求转发到应用代理层
location / {
proxy_pass http://app_proxy_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_set_header X-Forwarded-Proto $scheme;
# 限流配置
limit_req zone=api_limit burst=100 nodelay;
limit_conn_status 429;
}
}
# 应用代理层配置 - /etc/nginx/app.conf
# 负责 upstream 健康检查和负载均衡
upstream backend_php {
least_conn;
server 10.0.1.20:9000 weight=5;
server 10.0.1.21:9000 weight=5;
server 10.0.1.22:9000 backup;
# 主动健康检查
check interval=3000 rise=2 fall=2 timeout=1000 type=http;
check_http_send "HEAD /health HTTP/1.0
";
check_http_expect_alive http_2xx;
}
upstream backend_node {
ip_hash;
server 10.0.1.30:8080 weight=3;
server 10.0.1.31:8080 weight=3;
server 10.0.1.32:8080 weight=3;
}
server {
listen 80;
server_name api.example.com;
location /api/v1/ {
proxy_pass http://backend_php;
include /etc/nginx/conf.d/proxy_params.conf;
}
location /api/node/ {
proxy_pass http://backend_node;
include /etc/nginx/conf.d/proxy_params.conf;
}
}
主动健康检查配置
使用 nginx_upstream_check_module 实现主动健康检查,比被动检查更早发现故障节点,避免将请求发往已故障的后端。
# 加载模块(编译时添加)
# ./configure --add-module=/path/to/nginx_upstream_check_module
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
server 127.0.0.1:8082 backup;
# 主动健康检查配置
# interval: 检查间隔 3 秒
# rise: 连续 2 次成功则标记为 up
# fall: 连续 3 次失败则标记为 down
# timeout: 检查超时 1 秒
# type: 检查类型为 HTTP
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
# 发送的 HTTP 请求
check_http_send "HEAD /health HTTP/1.0
";
# 期望的响应状态码
check_http_expect_alive http_2xx http_3xx;
}
# 健康检查状态页面
server {
listen 8080;
server_name localhost;
location /upstream_status {
check_status;
access_log off;
allow 127.0.0.1;
deny all;
}
location /health {
return 200 "OK";
add_header Content-Type text/plain;
}
}
# 验证健康检查状态 $ curl http://127.0.0.1:8080/upstream_status Upstream server status: backend server 127.0.0.1:8080 up 1234567890 ms server 127.0.0.1:8081 down 5000 ms (3/3) server 127.0.0.1:8082 backup Total connections: 2345 upstream alive connections: 1234
灰度发布与权重调整
在升级后端服务时,使用 Nginx 的权重调整实现流量切换,而非直接替换。新版本接收小部分流量,观察无异常后逐步增加权重。
# 金丝雀发布配置
upstream backend_stable {
server 127.0.0.1:8080; # 当前稳定版本
}
upstream backend_canary {
server 127.0.0.1:8081; # 新版本,权重为 1
}
# 90% 流量到稳定版,10% 到金丝雀
upstream backend {
server 127.0.0.1:8080 weight=9;
server 127.0.0.1:8081 weight=1;
}
# 或者根据请求特征(如 Cookie、Header)分流
map $cookie_canary_version $backend_pool {
default http://backend_stable;
"v2" http://backend_canary;
}
location / {
proxy_pass $backend_pool;
include /etc/nginx/conf.d/proxy_params.conf;
}
# 监控金丝雀版本的错误率
$ awk'$9 ~ /5../ && /127.0.0.1:8081/ {count++} END {print "Canary 5xx:", count}'/var/log/nginx/access.log
Canary 5xx: 12
$ awk'$9 ~ /5../ && /127.0.0.1:8080/ {count++} END {print "Stable 5xx:", count}'/var/log/nginx/access.log
Stable 5xx: 3
# 金丝雀错误率略高,继续观察
限流与熔断
在 upstream 压力过大或响应变慢时,主动拒绝部分请求,防止雪崩效应。
# 限流配置
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
location /api/ {
# 令牌桶限流,burst=200 表示突发最多接受 200 个请求
# nodelay 不延迟处理,直接返回 503
limit_req zone=api_limit burst=200 nodelay;
# 连接数限制
limit_conn conn_limit 10;
proxy_pass http://backend;
proxy_next_upstream error timeout http_502 http_503;
}
}
# 熔断配置:基于响应时间自动降级
# 当 upstream 平均响应时间超过阈值时,返回降级响应
proxy_cache_bypass $upstream_http_x_downgrade;
4.2 配置模板
生产环境完整配置示例
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 65535;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time" '
'request_id="$request_id"';
access_log /var/log/nginx/access.log main buffer=16k flush=2s;
# 基础优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
# 超时配置
keepalive_timeout 65;
keepalive_requests 1000;
client_header_timeout 15s;
client_body_timeout 15s;
send_timeout 60s;
# 缓冲区配置
client_max_body_size 100m;
client_body_buffer_size 256k;
client_header_buffer_size 4k;
large_client_header_buffers 4 16k;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
# 隐藏 Nginx 版本号
server_tokens off;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# upstream 块定义
include /etc/nginx/conf.d/upstream/*.conf;
# 引入各站点的 server 配置
include /etc/nginx/sites-enabled/*.conf;
}
# /etc/nginx/conf.d/upstream/backend.conf
upstream backend_php {
least_conn;
server 127.0.0.1:9000 weight=5 max_fails=3 fail_timeout=10s;
server 127.0.0.1:9001 weight=5 max_fails=3 fail_timeout=10s;
server 127.0.0.1:9002 backup;
keepalive 32;
keepalive_requests 1000;
keepalive_timeout 60s;
}
upstream backend_static {
server 127.0.0.1:8080 weight=5;
server 127.0.0.1:8081 weight=5;
server 127.0.0.1:8082 backup;
keepalive 32;
}
upstream backend_api {
ip_hash;
server 10.0.1.20:8080;
server 10.0.1.21:8080;
server 10.0.1.22:8080;
# 主动健康检查
check interval=3000 rise=2 fall=2 timeout=1000 type=http;
check_http_send "HEAD /health HTTP/1.0
";
check_http_expect_alive http_2xx;
}
# /etc/nginx/sites-enabled/api.example.com.conf
server {
listen 80;
server_name api.example.com;
# 将 HTTP 请求重定向到 HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 访问日志
access_log /var/log/nginx/api.example.com.access.log main buffer=16k;
# 健康检查端点(不经过限流)
location = /health {
access_log off;
return 200 "OK
";
add_header Content-Type text/plain;
}
# API 路由
location /api/v1/ {
# 限流
limit_req zone=api_limit burst=100 nodelay;
limit_req_status 429;
# proxy 配置
proxy_pass http://backend_php;
proxy_http_version 1.1;
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;
proxy_set_header Connection "";
# 超时配置
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 120s;
# 缓冲区配置
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 8 256k;
proxy_busy_buffer_size 256k;
# 错误处理:后端故障时返回降级响应
error_page 502 503 = @fallback;
}
# 静态资源
location /static/ {
proxy_pass http://backend_static;
proxy_cache static_cache;
proxy_cache_valid 200 60m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating;
add_header X-Cache-Status $upstream_cache_status;
expires 7d;
add_header Cache-Control "public, immutable";
}
# 降级响应
location @fallback {
default_type application/json;
return 503 '{"code":503,"message":"Service temporarily unavailable","request_id":"$request_id"}';
}
# 429 Too Many Requests 响应
location = /429.html {
internal;
default_type application/json;
return 429 '{"code":429,"message":"Rate limit exceeded","retry_after":1}';
}
}
日志切割配置
# /etc/logrotate.d/nginx
/var/log/nginx/*.log{
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 nginx adm
sharedscripts
postrotate
[ -f /run/nginx.pid ] &&kill-USR1 $(cat /run/nginx.pid)
endscript
}
# 手动测试日志切割 $ logrotate -f /etc/logrotate.d/nginx $ ls -la /var/log/nginx/ total 1234 -rw-r----- 1 nginx adm 45678 Apr 24 00:00 access.log -rw-r----- 1 nginx adm 12345 Apr 24 00:00 access.log-20260424.gz -rw-r----- 1 nginx adm 23456 Apr 24 00:00 error.log -rw-r----- 1 nginx adm 8901 Apr 24 00:00 error.log-20260424.gz
性能测试基线
# 使用 wrk 进行基准性能测试 $ wrk -t4 -c100 -d60s --latency http://api.example.com/api/v1/health Running 1mtest@ http://api.example.com/api/v1/health 4 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 12.34ms 2.15ms 45.67ms 90.12% Req/Sec 2345.67 89.23 2500.00 85.00% Latency Distribution 50% 11.89ms 75% 13.45ms 90% 15.23ms 99% 25.67ms 1234567 requestsin60.00s, 234.56MBread Requests/sec: 20576.12 Transfer/sec: 3.91MB
# 使用 ab 进行连接测试
$ ab -n 10000 -c 1000 -g ab_results.tsv http://api.example.com/api/v1/health
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking api.example.com (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software: nginx/1.26.2
Server Hostname: api.example.com
Server Port: 443
SSL/TLS Protocol: TLSv1.3, ECDHE-RSA-AES256-GCM-SHA384, 256bit
Document Path: /api/v1/health
Document Length: 3 bytes
Concurrency Level: 1000
Time takenfortests: 12.345 seconds
Complete requests: 10000
Failed requests: 0
Non-2xx responses: 0
Total transferred: 2340000 bytes
HTML transferred: 30000 bytes
Requests per second: 810.23 [#/sec] (mean)
Time per request: 1234.567 [ms] (mean)
Time per request: 1.235 [ms] (mean, across all concurrent requests)
Transfer rate: 185.23 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 123 456 78.9 434 1234
Waiting: 123 455 78.9 433 1233
Total: 123 456 78.9 434 1234
Percentage of the requests served within a certain time (ms)
50% 434
66% 456
75% 478
80% 489
90% 534
95% 612
98% 723
99% 789
100% 1234 (longest request)
4.3 故障应急
502 故障的快速止血
故障发生时,第一目标是快速恢复服务,最小化业务影响。
# 1. 立即检查 error.log,确认 502 根因类型
$ tail -100 /var/log/nginx/error.log | grep -E"[error]"| tail -20
# 2. 快速检查后端服务状态
$ systemctl status php-fpm node-backend gunicorn 2>&1 | grep -E"Active:|Main PID:"
# 3. 如果后端服务挂了,立即重启
$ systemctl restart php-fpm
$ systemctl restart node-backend
# 4. 如果是单个 upstream server 故障,临时从 upstream 中移除
$ vim /etc/nginx/conf.d/upstream/backend.conf
# 注释掉故障 server,保留可用的
upstream backend {
server 127.0.0.1:9000;
# server 127.0.0.1:9001; # 临时禁用
}
$ nginx -t && nginx -s reload
# 5. 如果所有后端都不可用,启动备用服务
$ systemctl start emergency-backend
# 6. 扩大上游超时时间,减少 502 触发
$ sed -i's/proxy_read_timeout 120s;/proxy_read_timeout 300s;/'/etc/nginx/conf.d/upstream/*.conf
$ nginx -s reload
后端切换的手动操作
# 手动切换流量到备用后端
# 场景:主数据库故障,需要切换到从库
# 1. 备份当前 upstream 配置
$ cp /etc/nginx/conf.d/upstream/app.conf /etc/nginx/conf.d/upstream/app.conf.bak.$(date +%Y%m%d%H%M%S)
# 2. 修改 upstream 指向备用后端
$ cat > /etc/nginx/conf.d/upstream/app_standby.conf << 'EOF'
upstream backend_app {
server 10.0.2.20:8080 weight=5;
server 10.0.2.21:8080 weight=5;
server 10.0.2.22:8080 backup;
}
EOF
# 3. 测试配置语法
$ nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# 4. 优雅reload,不中断现有连接
$ nginx -s reload
# 5. 监控错误率变化
$ watch -n 1 'grep -c " 502 " /var/log/nginx/access.log'
# 6. 验证备用后端可用性
$ curl -s http://10.0.2.20:8080/health | grep OK
$ curl -s http://10.0.2.21:8080/health | grep OK
业务降级方案
当后端无法处理请求时,返回预设的降级响应而非 502,提升用户体验。
# 降级配置示例
upstream backend_primary {
server 127.0.0.1:8080;
server 127.0.0.1:8081 backup;
}
# 主服务不可用时返回缓存或静态降级内容
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid 500 502 503 504 1m;
location /api/ {
# 尝试主后端
proxy_pass http://backend_primary;
# 后端返回 502/503 时使用缓存
proxy_cache_use_stale error timeout updating http_502 http_503 http_504;
# 后端响应慢时使用缓存
proxy_cache_use_stale error timeout updating;
# 缓存刷新时间
proxy_cache_background_update on;
proxy_cache_lock on;
# 降级响应:如果缓存也没有,返回静态 JSON
error_page 502 503 504 = @degraded_response;
}
location @degraded_response {
default_type application/json;
# 返回最近缓存的响应(由 proxy_cache_valid 控制)
add_header X-Cache-Status "DEGRADED";
return 200 '{"code":200,"message":"Cached response","data":null,"degraded":true}';
}
五、扩展阅读与证据链
5.1 官方文档
Nginx 官方文档
Nginx 官方文档(英文):https://nginx.org/en/docs/
Nginx upstream 模块文档:https://nginx.org/en/docs/http/ngx_http_upstream_module.html
Nginx proxy 模块文档:https://nginx.org/en/docs/http/ngx_http_proxy_module.html
Nginx 故障排查指南:https://nginx.org/en/docs/support.html
PHP-FPM 官方文档
PHP-FPM 配置说明:https://www.php.net/manual/en/install.fpm.configuration.php
PHP-FPM 进程管理:https://www.php.net/manual/en/install.fpm.pm.php
第三方模块文档
nginx_upstream_check_module(阿里巴巴):https://github.com/yaoweibin/nginx_upstream_check_module
OpenResty + lua-upstream:https://github.com/openresty/lua-upstream-nginx-module
5.2 关键配置文件参考
nginx.conf 完整模板
# Nginx 1.26.x / 1.27.x 完整配置模板
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
worker_rlimit_sigpending 65535;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 65535;
use epoll;
multi_accept on;
accept_mutex on;
accept_mutex_delay 500ms;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 字符集
charset utf-8;
charset_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml;
# 日志格式(包含 upstream 详细信息)
log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time '
'uct="$upstream_connect_time" '
'uht="$upstream_header_time" '
'urt="$upstream_response_time" '
'request_id="$request_id" '
'upstream_addr="$upstream_addr" '
'upstream_status="$upstream_status"';
access_log /var/log/nginx/access.log main_ext buffer=32k flush=5s;
# 基础优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# 超时配置
keepalive_timeout 65;
keepalive_requests 1000;
client_header_timeout 15s;
client_body_timeout 15s;
send_timeout 60s;
lingering_timeout 5s;
reset_timedout_connection on;
# 缓冲区配置
client_max_body_size 100m;
client_body_buffer_size 256k;
client_header_buffer_size 4k;
large_client_header_buffers 4 32k;
connection_pool_size 256;
request_pool_size 4k;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml application/atom+xml application/rss+xml image/svg+xml;
# 安全配置
server_tokens off;
hide_server_token on;
# Open file cache
open_file_cache max=65535 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# 限制请求
limit_req_zone $binary_remote_addr zone=general:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=1000r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;
# 引入 upstream 配置
include /etc/nginx/conf.d/upstream/*.conf;
# 引入站点配置
include /etc/nginx/sites-enabled/*.conf;
}
PHP-FPM 配置参数说明
; /etc/php-fpm/www.conf 关键参数说明 ; 进程管理器类型 ; static - 固定数量的子进程 ; dynamic - 动态数量的子进程 ; ondemand - 按需创建子进程 pm = dynamic ; 子进程最大数量 pm.max_children = 50 ; 启动时创建的子进程数(dynamic 模式) pm.start_servers = 10 ; 最小空闲子进程数(dynamic 模式) pm.min_spare_servers = 5 ; 最大空闲子进程数(dynamic 模式) pm.max_spare_servers = 20 ; 子进程处理请求数上限,达到后重启(防止内存泄漏累积) pm.max_requests = 500 ; 子进程空闲超时 pm.process_idle_timeout = 10s ; 紧急重启阈值 emergency_restart_threshold = 10 emergency_restart_interval = 1m ; 进程控制超时(处理 SIGTERM 需要的时间) process_control_timeout = 10s ; PHP 内存限制 php_admin_value[memory_limit] = 256M php_admin_flag[log_errors] = on ; 慢查询日志 slowlog = /var/log/php-fpm/www-slow.log request_slowlog_timeout = 10s request_slowlog_trace_depth = 20 ; 健康检查 ping.path = /ping ping.response = pong ; Socket 配置 listen = /var/run/php-fpm/www.sock listen.owner = www-data listen.group = www-data listen.mode = 0660
systemd Service 文件
; /etc/systemd/system/php-fpm.service [Unit] Description=PHP FastCGI Process Manager Documentation=man:php-fpm(8) After=network.target [Service] Type=notify PIDFile=/run/php-fpm/php-fpm.pid ExecStart=/usr/sbin/php-fpm --nodaemonize --fpm-config /etc/php-fpm.conf ExecReload=/bin/kill -USR2 $MAINPID ExecStop=/bin/kill -SIGINT $MAINPID PrivateTmp=true Restart=always RestartSec=10s TimeoutStartSec=30s TimeoutStopSec=30s ; 资源限制 LimitNOFILE=65535 LimitNPROC=8192 ; OOM 策略 - 不阻止 OOM Killer OOMScoreAdjust=-500 [Install] WantedBy=multi-user.target
; /etc/systemd/system/nginx.service [Unit] Description=nginx - high performance web server Documentation=https://nginx.org/en/docs/ After=network.target remote-fs.target nss-lookup.target [Service] Type=forking PIDFile=/run/nginx.pid ExecStartPre=/usr/sbin/nginx -t -c /etc/nginx/nginx.conf ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s QUIT $MAINPID PrivateTmp=true Restart=always RestartSec=5s ; 资源限制 LimitNOFILE=65535 LimitCORE=infinity [Install] WantedBy=multi-user.target
5.3 常见错误码速查表
| errno | 系统调用错误 | 含义 | 常见场景 |
|---|---|---|---|
| 111 | ECONNREFUSED | 连接被拒绝 | upstream 端口未监听 |
| 110 | ETIMEDOUT | 连接超时 | upstream 无响应,防火墙拦截 |
| 113 | EHOSTUNREACH | 主机不可达 | IP 地址错误,网络不通 |
| 111 | ECONNREFUSED | 连接被拒绝 | PHP-FPM socket 权限错误 |
| 104 | ECONNRESET | 连接被重置 | upstream 进程崩溃 |
| 32 | EPIPE | 管道破裂 | upstream 提前关闭连接 |
| 2 | ENOENT | 文件不存在 | Unix socket 文件不存在 |
5.4 502 排查流程图(文字版)
发现 502 错误
|
v
检查 error.log 中的具体错误信息
|
+--->"connect() failed (111)"---> upstream 端口未监听
| |
| +---> 检查后端服务是否运行: systemctl status php-fpm/node-backend
| +---> 检查端口/socket 是否监听: ss -tlnp | grep 9000
| +---> 检查配置中 IP:端口是否正确
|
+--->"upstream prematurely closed"---> upstream 进程崩溃
| |
| +---> 检查 dmesg | grep -i oom(内存不足)
| +---> 检查后端错误日志(PHP fatal error、core dump)
| +---> 检查 upstream 是否被 OOM Killer 杀死
|
+--->"upstream timed out"---> 后端处理超时
| |
| +---> 检查后端处理时间(数据库查询、外部 API)
| +---> 调整 proxy_read_timeout
| +---> 优化后端性能或增加资源
|
+--->"no live upstreams"---> 所有 upstream 都不可用
|
+---> 检查所有 upstream server 的健康状态
+---> 可能是 max_fails 导致所有节点被暂时禁用
+-----> 等待 fail_timeout 过去或重启 upstream
六、自检清单
6.1 部署前检查清单
[ ] Nginx 配置文件语法验证通过:nginx -t
[ ] 后端服务已启动并监听正确端口
[ ] upstream 配置的 IP:端口与实际一致
[ ] 超时参数(proxy_connect_timeout、proxy_read_timeout)已根据业务调整
[ ] 缓冲区参数(proxy_buffer_size、proxy_buffers)已配置
[ ] keepalive 配置已启用(proxy_http_version 1.1 + Connection "")
[ ] socket 文件权限正确(listen.owner/listen.group)
[ ] SELinux/AppArmor 策略允许 Nginx 与后端通信
[ ] 日志级别设置为 warn 或 error
[ ] 健康检查已配置(被动或主动)
6.2 故障发生时的快速检查项
[ ] 查看 error.log,确认具体错误信息
[ ] 确认后端服务进程存活:systemctl status php-fpm
[ ] 确认端口/socket 监听:ss -tlnp | grep 9000
[ ] 测试后端可连接性:curl -v http://127.0.0.1:9000/health
[ ] 检查系统资源(CPU、内存、磁盘IO)
[ ] 检查 dmesg 中是否有 OOM Killer 记录
[ ] 检查 upstream 是否被标记为 down
6.3 复盘与预防检查项
[ ] 错误是否可复现
[ ] 根因是否已定位并解决
[ ] 是否有监控告警覆盖同类问题
[ ] 是否需要调整超时/缓冲区等参数
[ ] 是否需要增加资源或优化架构
[ ] 是否更新了 SOP 和运维文档
附录 A:错误日志关键字索引
| 关键字 | 根因 | 处理优先级 |
|---|---|---|
| connect() failed (111) | upstream 未监听端口 | P0 - 立即处理 |
| upstream prematurely closed | upstream 崩溃 | P0 - 立即处理 |
| upstream timed out | 后端响应超时 | P1 - 优化超时 |
| no live upstreams | 所有 upstream 不可用 | P0 - 立即处理 |
| buffer is too small | 缓冲区配置不足 | P1 - 调整缓冲区 |
| Permission denied | 权限问题 | P0 - 立即处理 |
| connection reset | upstream 强制断开 | P1 - 检查 upstream 健康 |
| broken header | upstream 返回损坏响应 | P1 - 检查 upstream |
附录 B:命令速查卡片
# Nginx 基础操作
nginx -t # 测试配置语法
nginx -s reload # 优雅重载配置
nginx -s stop # 快速关闭
nginx -s quit # 优雅关闭
nginx -V # 查看版本和编译参数
# 日志分析
tail -f /var/log/nginx/error.log | grep 502
awk'$9==502 {count++} END {print count}'/var/log/nginx/access.log
grep -C 5"502"/var/log/nginx/error.log
# 后端服务检查
systemctl status php-fpm
ss -tlnp | grep -E':(80|443|9000)'
ps aux | grep php-fpm
netstat -ant | grep :8080 | wc -l
# 健康检查
curl -v http://127.0.0.1:8080/health
SCRIPT_NAME=/ping cgi-fcgi -bind-connect /var/run/php-fpm/www.sock
nc -zv 127.0.0.1 9000 -w 3
# 性能监控
ss -s # 连接统计
top -b -n 1 | head -20 # 系统资源
nginx -s reopen # 重新打开日志文件(配合 logrotate)
附录 C:配置参数参考表
upstream 指令
| 指令 | 默认值 | 说明 |
|---|---|---|
| server | - | 定义 upstream server,格式:address [parameters] |
| weight | 1 | 权重,用于加权轮询 |
| max_fails | 1 | 失败次数阈值,超过后标记为不可用 |
| fail_timeout | 10s | 失败超时时间,同时也是不可用持续时间 |
| backup | - | 标记为备用服务器 |
| down | - | 标记为永久不可用 |
| keepalive | - | keepalive 连接池大小 |
| keepalive_requests | 1000 | 单连接最大请求数 |
| keepalive_timeout | 60s | keepalive 空闲超时 |
| slow_start | 0 | 慢启动时间(商业版 Nginx Plus) |
proxy 指令
| 指令 | 默认值 | 说明 |
|---|---|---|
| proxy_pass | - | 指定 upstream 或 URL |
| proxy_http_version | 1.0 | HTTP 版本 |
| proxy_connect_timeout | 60s | 连接超时 |
| proxy_send_timeout | 60s | 发送请求超时 |
| proxy_read_timeout | 60s | 读取响应超时 |
| proxy_buffer_size | 4k/8k | 响应头缓冲区 |
| proxy_buffers | 8 4k/8k | 响应体缓冲区 |
| proxy_busy_buffer_size | proxy_buffer_size | 忙碌缓冲区 |
| proxy_buffering | on | 是否启用缓冲 |
| proxy_max_temp_file_size | 1024m | 临时文件总大小 |
| proxy_temp_file_write_size | proxy_buffer_size | 单次写入临时文件大小 |
| proxy_set_header | Host $proxy_host | 设置请求头 |
| proxy_next_upstream | error timeout | 故障转移条件 |
本文档由运维团队基于多年生产环境排障经验整理,涵盖 Nginx 502 错误的识别、诊断、处理的完整方法论。如有补充或修正,请提交 PR 至内部文档仓库。
-
Linux
+关注
关注
88文章
11825浏览量
219611 -
服务器
+关注
关注
14文章
10376浏览量
91777 -
nginx
+关注
关注
0文章
196浏览量
13224
原文标题:Nginx 502 Bad Gateway:从日志里挖出深藏的配置坑
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
nginx重启命令linux步骤是什么?
分享nginx 502的解决方法
科普系列:CAN总线错误帧及排查方法简介
如何用示波器排查CAN的各种错误帧呢?
玩转Nginx日志管理:高效排查问题的终极指南
云原生环境里Nginx的故障排查思路
电商API常见错误排查指南:避免集成陷阱
Nginx 502 Bad Gateway错误的成因和排查方法
评论