一、概述
1.1 背景介绍
在运维工作中,Nginx的502和504错误可以说是“老熟人”了。业务高峰时突然大面积502,或是某个接口偶发504导致用户投诉,这些场景想必很多运维工程师都经历过。
虽然这两个错误码同属网关错误,但背后的根因和排查思路截然不同。常见误区是遇到502就重启PHP-FPM,碰上504就盲目加大超时时间,这种“头痛医头”的做法往往无法根治问题。
本文将系统梳理Nginx 502/504错误的排查逻辑,从核心原理、详细排查步骤到优化方案,并结合我们团队在实际生产环境中积累的运维经验,提供一套完整的解决方案和一个实用的一键排查脚本。
1.2 502与504的区别
首先要厘清这两个状态码的本质区别:
502 Bad Gateway(错误网关)
- Nginx作为反向代理,已将请求转发给后端服务。
- 但后端返回了一个Nginx无法理解的响应,或者直接断开了连接。
- 通俗理解:后端服务挂了,或者返回了异常数据。
504 Gateway Timeout(网关超时)
- Nginx作为反向代理,已将请求转发给后端服务。
- 但在等待了设定的超时时间后,后端仍未返回任何响应。
- 通俗理解:后端处理太慢,Nginx等不及了。
一句话总结:502是后端“不行了”,504是后端“太慢了”。
1.3 适用场景
- 场景一:PHP-FPM作为后端(经典LNMP架构)。
- 场景二:Nginx反向代理到Java/Go/Python等自研应用服务。
- 场景三:Nginx作为负载均衡器,代理到多个上游(upstream)服务。
- 场景四:WebSocket等长连接场景。
1.4 环境要求
| 组件 |
版本要求 |
说明 |
| Nginx |
1.14+ |
本文命令适用于主流版本 |
| 操作系统 |
CentOS 7+ / Ubuntu 18.04+ |
|
| 后端服务 |
PHP-FPM / Tomcat / 自研服务 |
示例以PHP-FPM为主 |
二、详细步骤
2.1 502错误排查
遇到502错误,建议按以下顺序排查,可覆盖绝大多数情况。
◆ 2.1.1 检查后端服务状态
502最常见的原因就是后端服务进程不在了。首先确认服务是否在运行:
# 检查PHP-FPM状态
systemctl status php-fpm
ps aux | grep php-fpm | grep -v grep
# 检查Java服务(如Tomcat)
systemctl status tomcat
ps aux | grep java | grep -v grep
# 检查端口是否在监听
ss -tlnp | grep 9000 # PHP-FPM默认端口
ss -tlnp | grep 8080 # Tomcat/Spring Boot常见端口
# 如果使用Unix Socket连接
ls -la /run/php-fpm/www.sock
# 确认socket文件存在且权限正确(Nginx进程用户可读)
经验之谈:曾遇到PHP-FPM父进程还在,但socket文件被误删的情况,Nginx无法连接同样报502。因此,不能只看进程是否存在,还需确认监听端点(端口或socket)正常。
◆ 2.1.2 检查Nginx错误日志
Nginx的error.log是定位502问题的金矿,所有连接后端的错误信息都会记录于此。
# 实时查看错误日志
tail -f /var/log/nginx/error.log
# 过滤与502/上游连接相关的错误
grep "502\|upstream\|connect\|failed" /var/log/nginx/error.log | tail -50
常见的错误信息及含义:
| 错误信息 |
含义 |
解决方向 |
connect() failed (111: Connection refused) |
连接被拒绝,后端服务未启动或端口错误 |
检查后端服务状态与端口 |
connect() failed (113: No route to host) |
网络不可达 |
检查防火墙、网络路由 |
upstream prematurely closed connection |
上游(后端)提前关闭了连接 |
后端可能因OOM(内存溢出)崩溃 |
recv() failed (104: Connection reset by peer) |
对端重置了连接 |
后端处理出现异常 |
no live upstreams |
所有配置的上游服务器都不可用 |
检查所有后端节点 |
◆ 2.1.3 检查PHP-FPM状态(LNMP场景)
如果后端是PHP-FPM,以下检查至关重要:
# 查看PHP-FPM状态页(需先配置开启)
curl http://127.0.0.1/php-fpm-status
# 查看PHP-FPM总进程数
ps aux | grep "php-fpm" | grep -v grep | wc -l
# 查看PHP-FPM实际处理请求的子进程状态
ps aux | grep "php-fpm: pool" | grep -v grep
# 检查PHP-FPM错误日志
tail -100 /var/log/php-fpm/www-error.log
开启PHP-FPM状态页的配置:
; /etc/php-fpm.d/www.conf
pm.status_path = /php-fpm-status
# nginx配置中增加一个location来访问状态页
location /php-fpm-status {
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
allow 127.0.0.1;
deny all;
}
PHP-FPM进程池耗尽是502的常见原因。如果状态页显示所有子进程都是“active”(忙碌)状态,且“idle processes”(空闲进程)为0,说明进程池已满,新请求无法被处理:
# 状态页输出示例:
pool: www
process manager: dynamic
start time: 01/Jan/2024:10:00:00 +0800
start since: 86400
accepted conn: 123456
listen queue: 0
max listen queue: 100
listen queue len: 128
idle processes: 0 # 危险信号:空闲进程为0
active processes: 50 # 活跃进程数
total processes: 50
max active processes: 50
max children reached: 10 # 达到最大进程数的次数,增长快说明进程不足
slow requests: 5
◆ 2.1.4 检查连接数和资源限制
有时服务进程还在,但系统或进程本身的资源达到上限,也会导致502。
# 查看系统已使用的文件描述符(连接本质是文件)
cat /proc/sys/fs/file-nr
# 输出:已用数量 未用数量 最大值
# 查看Nginx worker进程的总连接数概况
ss -s
# 查看特定后端端口(如9000)的当前连接数
ss -ant | grep :9000 | wc -l
# 查看PHP-FPM主进程使用的文件描述符数量
ls /proc/$(pgrep -o php-fpm)/fd | wc -l
# 查看当前shell的资源限制(如最大打开文件数)
ulimit -n
# 查看系统级别的资源限制配置
cat /etc/security/limits.conf | grep -v "^#"
2.2 504错误排查
504错误意味着后端处理时间超过了Nginx设置的超时阈值。
◆ 2.2.1 确认超时配置
首先检查当前Nginx中关于超时的各项配置。
# 查看Nginx配置中的所有超时参数
grep -r "timeout" /etc/nginx/ | grep -v "#"
Nginx中影响504的关键超时参数如下:
# 与后端服务器建立连接的超时时间(默认60秒)
proxy_connect_timeout 60s;
# 定义从后端服务器读取响应数据的超时时间(默认60秒)
proxy_read_timeout 60s;
# 定义将请求发送到后端服务器的超时时间(默认60秒)
proxy_send_timeout 60s;
# FastCGI(PHP-FPM)场景下的对应参数
fastcgi_connect_timeout 60s;
fastcgi_read_timeout 60s;
fastcgi_send_timeout 60s;
◆ 2.2.2 分析后端实际响应时间
仅看配置不够,需要知道后端处理请求实际耗时。可以配置Nginx的日志格式,记录详细的上下游时间。
# 在 nginx.conf 的 http 块中定义一个详细日志格式
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
# 在 server 或 location 块中使用该格式
access_log /var/log/nginx/access.log detailed;
各时间字段含义:
$request_time:Nginx处理请求的总耗时。
$upstream_connect_time:Nginx与后端建立连接所花费的时间。
$upstream_header_time:Nginx从后端收到响应头所花费的时间。
$upstream_response_time:Nginx从后端收到完整响应所花费的时间。这个时间超过 proxy_read_timeout 就会触发504。
分析慢请求:
# 找出响应时间超过5秒的请求(假设日志格式已包含urt)
awk '$NF > 5 {print $0}' /var/log/nginx/access.log | tail -20
# 按接口(URL)统计平均响应时间(简化示例,实际需根据日志格式调整)
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
◆ 2.2.3 排查后端处理慢的原因
504的根源在于后端。需要根据后端的类型进行针对性排查。
# 如果是PHP,开启并查看慢执行日志
# 编辑 /etc/php-fpm.d/www.conf
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 3s # 执行超过3秒的请求会被记录
# 查看PHP慢日志
tail -100 /var/log/php-fpm/www-slow.log
# 如果是MySQL慢查询导致
tail -100 /var/log/mysql/slow.log
# 如果是Java应用,可以使用jstack抓取线程转储,分析卡在何处
jstack <PID> > /tmp/thread_dump.txt
◆ 2.2.4 网络延迟检查
如果Nginx与后端服务部署在不同机器,网络也可能是瓶颈。
# 基础ping测试,检查延迟和丢包
ping -c 10 后端服务器IP
# TCP连接测试,测量建立TCP连接的时间
time nc -zv 后端服务器IP 端口
# 使用mtr进行更详细的网络路径诊断
mtr --report 后端服务器IP
2.3 常见原因汇总
◆ 502常见原因
| 原因 |
表现 |
解决方案 |
| 后端服务崩溃 |
进程不存在,端口无监听 |
重启服务,并排查崩溃原因(日志、OOM等) |
| PHP-FPM进程池耗尽 |
活跃进程数达到 pm.max_children |
增加进程数上限,或优化代码减少请求处理时间 |
| Socket文件权限问题 |
Nginx worker进程用户无权访问socket文件 |
检查并修正socket文件的所有者和权限 |
| 系统连接数耗尽 |
大量TIME_WAIT状态连接 |
调整net.ipv4.tcp_tw_reuse等内核参数 |
| 后端进程被OOM Killer终止 |
dmesg或系统日志中有oom-killer记录 |
增加内存,或优化应用内存使用 |
◆ 504常见原因
| 原因 |
表现 |
解决方案 |
| SQL慢查询 |
PHP慢日志或MySQL慢日志中存在耗时长的SQL |
优化SQL语句,添加合适的索引 |
| 调用外部API/服务慢 |
依赖的第三方接口响应缓慢 |
增加调用超时控制,考虑异步化或降级策略 |
| 代码逻辑问题(死循环/阻塞) |
CPU使用率高但请求不返回 |
审查应用代码逻辑 |
| 资源竞争(锁等待) |
大量进程等待同一个锁(数据库行锁、文件锁等) |
优化锁策略,减少临界区 |
| Nginx超时配置过短 |
业务逻辑本身就需要较长时间处理(如文件上传、报表生成) |
根据业务实际情况,合理增加超时时间 |
2.4 解决方案
◆ 2.4.1 502解决方案
PHP-FPM进程池优化配置示例:
; /etc/php-fpm.d/www.conf
[www]
...
; 进程管理方式,dynamic为动态管理
pm = dynamic
; 最大子进程数,根据服务器内存计算(内存总量 / 单进程平均内存占用)
pm.max_children = 100
; 启动时创建的子进程数
pm.start_servers = 20
; 保证最少空闲进程数,用于应对突发请求
pm.min_spare_servers = 10
; 保证最多空闲进程数,过多会被回收
pm.max_spare_servers = 30
; 每个子进程处理多少请求后自动重启,防止内存泄漏累积
pm.max_requests = 500
系统连接数优化(内核参数):
# 编辑 /etc/sysctl.conf
# 扩大本地端口范围
net.ipv4.ip_local_port_range = 1024 65535
# 允许复用处于TIME_WAIT状态的连接
net.ipv4.tcp_tw_reuse = 1
# 减少TIME_WAIT状态的超时时间
net.ipv4.tcp_fin_timeout = 30
# 提高系统同时接受连接的最大队列长度
net.core.somaxconn = 65535
# 使配置生效
sysctl -p
◆ 2.4.2 504解决方案
Nginx超时与缓冲优化配置示例:
location /api/ {
proxy_pass http://backend_server;
# 连接后端超时,不宜过长
proxy_connect_timeout 10s;
# 读取后端响应超时,根据业务调整(如报表生成可设长)
proxy_read_timeout 120s;
# 发送请求到后端超时
proxy_send_timeout 60s;
# 缓冲区优化,提升性能
proxy_buffer_size 64k;
proxy_buffers 8 64k;
proxy_busy_buffers_size 128k;
}
负载均衡(upstream)优化配置示例:
upstream backend {
# 使用最少连接数算法,比默认轮询更均衡
least_conn;
server 192.168.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 backup; # 备份节点,当主节点全挂时启用
# 启用到后端的连接复用,减少频繁建连开销
keepalive 32;
}
server {
location / {
proxy_pass http://backend;
# 启用keepalive必须的HTTP版本和头部设置
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
三、示例代码和配置
3.1 Nginx优化配置示例
以下是一个综合性的生产环境Nginx主配置 (nginx.conf) 示例:
user nginx;
worker_processes auto; # 自动设置为CPU核数
worker_rlimit_nofile 65535; # 提高worker进程能打开的文件数上限
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 65535;
use epoll; # Linux高效事件模型
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"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000; # 一个连接最多处理的请求数
# Gzip压缩配置
gzip on;
gzip_min_length 1k;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# 上游服务定义
upstream php_backend {
server unix:/run/php-fpm/www.sock;
keepalive 16;
}
upstream api_backend {
least_conn;
server 192.168.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 weight=5 max_fails=3 fail_timeout=30s;
keepalive 32;
}
# 包含其他server配置
include /etc/nginx/conf.d/*.conf;
}
3.2 PHP-FPM优化配置示例
; /etc/php-fpm.d/www.conf
[www]
user = nginx
group = nginx
; 使用Unix Socket,比TCP性能更好
listen = /run/php-fpm/www.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
; 进程管理
pm = dynamic
pm.max_children = 100
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.max_requests = 500
pm.process_idle_timeout = 10s
; 状态监控页
pm.status_path = /php-fpm-status
ping.path = /php-fpm-ping
ping.response = pong
; 慢日志
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 3s
; 请求终止超时(全局)
request_terminate_timeout = 120s
; 错误日志
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
3.3 一键排查脚本
这是一个集成的Bash脚本,可以快速检查与Nginx 502/504相关的多项指标,非常适合在故障发生时快速收集信息。
#!/bin/bash
# nginx_diagnose.sh - Nginx 502/504 综合排查脚本
# 使用方法: bash nginx_diagnose.sh
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "========================================"
echo "Nginx 502/504 一键排查脚本"
echo "检查时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================"
echo ""
echo -e "${YELLOW}=== 1. Nginx服务状态 ===${NC}"
if systemctl is-active --quiet nginx; then
echo -e "${GREEN}[OK] Nginx正在运行${NC}"
else
echo -e "${RED}[ERROR] Nginx未运行${NC}"
fi
nginx -t 2>&1 | head -5
echo ""
echo -e "${YELLOW}=== 2. 后端服务状态检查 ===${NC}"
# 检查PHP-FPM
if command -v php-fpm &> /dev/null; then
if systemctl is-active --quiet php-fpm; then
echo -e "${GREEN}[OK] PHP-FPM正在运行${NC}"
fpm_count=$(ps aux | grep "php-fpm: pool" | grep -v grep | wc -l)
echo " 当前PHP-FPM子进程数: $fpm_count"
else
echo -e "${RED}[ERROR] PHP-FPM未运行${NC}"
fi
fi
# 检查常见后端端口监听情况
for port in 9000 8080 3000 5000; do
if ss -tlnp | grep -q ":$port "; then
echo -e "${GREEN}[OK] 端口 $port 已被监听${NC}"
fi
done
echo ""
echo -e "${YELLOW}=== 3. 最近的Nginx错误日志 (502/504) ===${NC}"
if [ -f /var/log/nginx/error.log ]; then
error_count=$(grep -c "502\|504\|upstream" /var/log/nginx/error.log 2>/dev/null || echo 0)
echo "最近错误总数: $error_count"
echo "最近10条相关错误:"
grep -E "502|504|upstream|connect" /var/log/nginx/error.log | tail -10
else
echo "未找到Nginx错误日志文件"
fi
echo ""
echo -e "${YELLOW}=== 4. 系统连接数统计 ===${NC}"
echo "当前TCP连接摘要:"
ss -s
echo ""
echo "各状态连接数统计:"
ss -ant | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn
echo ""
echo -e "${YELLOW}=== 5. 系统资源概览 ===${NC}"
echo "CPU使用率 (快照):"
top -bn1 | head -5
echo ""
echo "内存使用情况:"
free -h
echo ""
echo "磁盘使用情况:"
df -h | grep -v tmpfs
echo ""
echo -e "${YELLOW}=== 6. 文件描述符使用情况 ===${NC}"
echo "系统级别文件描述符:"
cat /proc/sys/fs/file-nr
echo "Nginx各进程文件描述符数量:"
for pid in $(pgrep nginx); do
echo "PID $pid: $(ls /proc/$pid/fd 2>/dev/null | wc -l) 个fd"
done
echo ""
echo -e "${YELLOW}=== 7. Nginx超时配置检查 ===${NC}"
grep -r "timeout" /etc/nginx/ 2>/dev/null | grep -v "#" | head -20
echo ""
echo -e "${YELLOW}=== 8. Nginx Upstream配置检查 ===${NC}"
grep -r "upstream" /etc/nginx/ 2>/dev/null | grep -v "#" | head -20
echo ""
echo -e "${YELLOW}=== 9. 最近的慢请求 (>3秒) ===${NC}"
if [ -f /var/log/nginx/access.log ]; then
awk -F'rt=' '{if(NF>1){split($2,a," ");if(a[1]>3)print $0}}' /var/log/nginx/access.log | tail -10
fi
echo ""
echo "========================================"
echo "检查完成。请重点关注以上标红[ERROR]项及错误日志内容。"
echo "========================================"
使用方法:
chmod +x nginx_diagnose.sh
./nginx_diagnose.sh
3.4 实际应用案例
◆ 案例一:高并发场景下的502
场景:电商大促期间,流量激增,前端出现大面积502错误。
排查过程:
-
查看Nginx错误日志:
tail -f /var/log/nginx/error.log
# 输出: connect() to unix:/run/php-fpm/www.sock failed (11: Resource temporarily unavailable)
错误11通常表示资源临时不可用,指向连接问题。
-
检查PHP-FPM进程池:
ps aux | grep "php-fpm: pool" | wc -l
# 输出: 50
进程数已达到配置的最大值(pm.max_children=50)。
-
查看PHP-FPM状态页:
curl http://127.0.0.1/php-fpm-status
# 关键输出:
# pool: www
# max children reached: 156
# idle processes: 0
# active processes: 50
max children reached数值很高且不断增长,idle processes为0,确认进程池已耗尽,新请求无进程可用。
解决方案(临时应急):
- 根据服务器剩余内存,适当提高
pm.max_children(例如从50调整到100)。
- 同时按比例增加
pm.start_servers、pm.max_spare_servers 等参数。
- 执行
systemctl reload php-fpm 平滑重载配置。
后续优化:
- 优化PHP代码,减少单个请求的处理时间和内存占用。
- 引入Redis缓存,减少对数据库的直接查询。
- 建立运维监控和自动伸缩机制。
◆ 案例二:慢查询导致的504
场景:某个报表导出接口频繁报504超时,其他接口正常。
排查过程:
-
分析Nginx访问日志:
grep "api/report/export" /var/log/nginx/access.log | awk -F'rt=' '{print $2}' | sort -rn | head -5
# 输出: 120.005, 120.001, 119.998
响应时间集中在120秒,正好是Nginx proxy_read_timeout 的默认值,说明是触发了超时。
-
检查PHP-FPM慢日志:
tail -20 /var/log/php-fpm/www-slow.log
# 脚本在执行一个复杂的SQL查询时卡住。
-
检查MySQL慢查询日志:
tail -30 /var/log/mysql/slow.log
# 发现一条对百万级大表进行全表扫描且无索引的SELECT语句,执行耗时超过100秒。
解决方案:
- 紧急限流:在Nginx层对该报表接口进行限流,避免拖垮整个数据库。
-
优化SQL:
-- 优化后 (添加索引: ALTER TABLE order_detail ADD INDEX idx_time_status (create_time, order_status);)
SELECT id, order_id, amount, create_time FROM order_detail
WHERE create_time > '2024-01-01' AND order_status = 'completed'
ORDER BY create_time DESC LIMIT 1000;
- 应用优化:对于超大数据量的导出,改为异步任务处理,完成后通知用户下载。
四、最佳实践和注意事项
4.1 最佳实践
◆ 4.1.1 Nginx性能调优参数
# 主配置文件优化项
user nginx;
worker_processes auto; # 与CPU核心数一致
worker_rlimit_nofile 65535; # 提高单个worker能打开的文件数上限
events {
worker_connections 65535; # 每个worker的最大连接数
use epoll; # Linux高效网络模型
multi_accept on; # 一次接受多个新连接
}
http {
# 文件描述符缓存,减少磁盘I/O
open_file_cache max=65535 inactive=60s;
open_file_cache_valid 80s;
open_file_cache_min_uses 1;
open_file_cache_errors off;
# 反向代理缓冲区优化
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_temp_file_write_size 256k;
}
◆ 4.1.2 后端服务优化建议
◆ 4.1.3 监控与告警配置
建议使用Prometheus + Grafana等监控体系。以下为示例告警规则:
# Prometheus告警规则 (alert.rules.yml)
groups:
- name: nginx_errors
rules:
- alert: NginxHigh5xxErrorRate
expr: rate(nginx_http_requests_total{status=~"5.."}[5m]) / rate(nginx_http_requests_total[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "Nginx 5xx错误率过高"
description: "实例 {{ $labels.instance }} 5xx错误率超过5%,当前值 {{ $value | humanizePercentage }}"
- alert: NginxUpstreamResponseTimeout
expr: nginx_upstream_response_time_seconds{upstream!=""} > 10
for: 2m
labels:
severity: warning
annotations:
summary: "Nginx上游响应时间过长"
description: "上游 {{ $labels.upstream }} 响应时间超过10秒"
4.2 注意事项
◆ 4.2.1 配置注意事项
- 超时时间非越长越好:将
proxy_read_timeout 设为300秒可能掩盖真正的问题,应优先优化慢请求。
- 进程/线程数平衡:PHP-FPM、Java应用线程池等并非配置越多越好,需考虑内存和CPU上下文切换开销。
- 平滑重载:修改Nginx配置后使用
nginx -s reload,而非 restart,以保障连接不中断。
- 配置语法检查:执行
nginx -t 确认配置无误后再重载。
◆ 4.2.2 常见配置错误
| 错误现象 |
可能原因 |
解决方案 |
| 修改配置后reload无效 |
配置文件存在语法错误,reload失败但进程未退出 |
使用 nginx -t 严格测试,查看error.log |
| 调整超时后504依旧 |
配置未在正确层级生效(http/server/location优先级不同) |
确认超时参数配置在匹配的 location 块中 |
| Upstream服务器无法连接 |
防火墙、安全组或SELinux策略阻止 |
检查 firewalld/iptables 和 getsebool、sestatus |
| Socket文件权限错误 |
Nginx worker用户(如nginx)与PHP-FPM socket文件用户/组不一致 |
确保 listen.owner、listen.group 与Nginx用户匹配 |
◆ 4.2.3 兼容性与版本问题
- HTTP版本:启用
keepalive 连接复用需要后端和Nginx都支持HTTP/1.1协议,并设置 proxy_http_version 1.1。
- Unix Socket vs TCP:Unix Socket性能更低耗,但仅适用于Nginx与后端在同一台主机的情况。
- PHP版本差异:PHP 5.3、7.x、8.x 的PHP-FPM配置参数可能略有不同,迁移时需注意。
五、故障排查和监控
5.1 日志分析技巧
# 统计每分钟的502/504错误数量
awk '$9==502 || $9==504 {print $4}' /var/log/nginx/access.log | cut -d: -f1,2 | sort | uniq -c
# 找出返回502最多的URL(前10名)
awk '$9==502 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
# 分析请求响应时间分布(快速评估性能)
awk -F'rt=' '{
if(NF>1){
split($2,a," ");
t=a[1];
if(t<0.1) fast++;
else if(t<1) normal++;
else if(t<3) slow++;
else too_slow++;
}
}
END {
printf "Fast(<0.1s): %d\nNormal(<1s): %d\nSlow(1-3s): %d\nToo Slow(>3s): %d\n", fast, normal, slow, too_slow
}' /var/log/nginx/access.log
5.2 启用Nginx状态监控
# 在Nginx server块中配置
location /nginx_status {
stub_status on; # 启用状态模块
access_log off;
allow 127.0.0.1; # 仅允许本机访问,或监控服务器IP
deny all;
}
访问 http://your-server/nginx_status 可看到:
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
Active connections:当前活跃连接总数。
Reading:正在读取请求头的连接数。
Writing:正在向客户端发送响应的连接数。
Waiting:已处理完请求,处于 keep-alive 空闲状态的连接数。这是反映并发能力的关键指标之一。
5.3 简易告警脚本示例
一个基于日志分析的简单阈值告警脚本:
#!/bin/bash
# nginx_error_alert.sh
LOG_FILE="/var/log/nginx/access.log"
ERROR_THRESHOLD=10 # 每分钟5xx错误数阈值
SLOW_THRESHOLD=3 # 秒,慢请求阈值
SLOW_COUNT_THRESHOLD=5 # 每分钟慢请求数阈值
CURRENT_MINUTE=$(date -d '1 minute ago' '+%d/%b/%Y:%H:%M')
# 1. 检查5xx错误
ERROR_COUNT=$(awk -v min="$CURRENT_MINUTE" '$4 ~ min && $9 ~ /^5/ {count++} END {print count+0}' "$LOG_FILE")
if [ $ERROR_COUNT -gt $ERROR_THRESHOLD ]; then
echo "[警报] 过去1分钟检测到 $ERROR_COUNT 个5xx错误,超过阈值 $ERROR_THRESHOLD。" | tee -a /tmp/nginx_alert.log
# 此处可集成邮件、钉钉、企业微信等告警发送逻辑
fi
# 2. 检查慢请求数量 (假设日志格式包含rt=)
SLOW_COUNT=$(awk -v min="$CURRENT_MINUTE" -v thr="$SLOW_THRESHOLD" '$4 ~ min && match($0, /rt=([0-9.]+)/, m) && m[1] > thr {count++} END {print count+0}' "$LOG_FILE")
if [ $SLOW_COUNT -gt $SLOW_COUNT_THRESHOLD ]; then
echo "[警告] 过去1分钟检测到 $SLOW_COUNT 个超过 ${SLOW_THRESHOLD}s 的慢请求。" | tee -a /tmp/nginx_alert.log
fi
可将此脚本加入crontab,每分钟执行一次。
六、总结
6.1 核心要点回顾
- 502 Bad Gateway:根源在后端服务不可用(崩溃、进程池满、资源耗尽)。排查关键是看Nginx的error.log和检查后端进程状态。
- 504 Gateway Timeout:根源在后端处理超时(SQL慢、外部API慢、代码阻塞)。排查关键是分析Nginx访问日志中的上游响应时间,并结合后端慢日志。
- 科学排查:遵循从现象(错误码)到日志(Nginx error/access log),再到后端状态(进程、资源、慢日志)的排查路径,避免盲目操作。
- 治本而非治标:不要习惯性地“重启服务”或“调大超时”,而应定位并解决引发问题的根本原因。
6.2 进阶学习方向
- 全链路追踪:接入SkyWalking、Jaeger、Zipkin等APM工具,可视化请求在Nginx、应用、数据库、缓存之间的完整路径和耗时,精准定位瓶颈。
- Service Mesh:了解Istio、Linkerd等服务网格技术,它们提供了更细粒度、更强大的流量管理、观测和安全性能力。
- 压力测试:定期使用wrk、ab、jmeter等工具对系统进行压测,提前发现性能瓶颈和容量上限。
- 弹性伸缩:在Kubernetes等容器化环境中,配置HPA(Horizontal Pod Autoscaler),实现基于CPU、内存或自定义指标(如QPS)的自动扩缩容。
6.3 参考资料
- Nginx官方文档:Module ngx_http_proxy_module (代理模块)
- PHP-FPM官方配置说明
- 操作系统性能调优指南(针对Linux内核参数)
希望这份涵盖原理、步骤、脚本和案例的指南,能帮助你在遇到棘手的Nginx网关错误时,更快地定位和解决问题。技术问题的排查能力,正是在一次次这样的实战中积累起来的。欢迎在云栈社区分享你的排查经验和遇到的疑难杂症。
附录
A. 命令速查表
# ---------- Nginx 相关 ----------
nginx -t # 测试配置文件语法
nginx -s reload # 平滑重载配置(不中断服务)
nginx -s stop # 快速停止
tail -f /var/log/nginx/error.log # 实时查看错误日志
# ---------- PHP-FPM 相关 ----------
systemctl status php-fpm # 查看服务状态
systemctl reload php-fpm # 平滑重载PHP-FPM配置
ps aux | grep php-fpm # 查看进程信息
tail -f /var/log/php-fpm/www-slow.log # 查看慢执行日志
# ---------- 网络连接排查 ----------
ss -antup # 查看所有TCP/UDP连接及对应进程
ss -s # 查看连接统计摘要
netstat -tlnp # 传统方式查看监听端口(如未安装ss)
lsof -i :80 # 查看占用80端口的进程
# ---------- 日志分析常用 ----------
grep "502\|504" /var/log/nginx/error.log # 过滤错误
awk '$9==502{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn # 统计502的URL
# 分析最近1小时请求状态码分布
awk -v d="$(date -d '1 hour ago' '+[%d/%b/%Y:%H')" '$4 ~ d {code[$9]++} END{for(c in code) print c, code[c]}' access.log
B. HTTP状态码速查(网关相关)
| 状态码 |
含义 |
Nginx视角常见原因 |
| 400 |
Bad Request |
客户端请求语法错误,Nginx无法理解。 |
| 403 |
Forbidden |
访问被拒绝(权限不足、IP限制等)。 |
| 404 |
Not Found |
请求的资源在Nginx上未找到。 |
| 499 |
Client Closed Request |
客户端在Nginx处理期间主动关闭了连接。常出现在后端处理慢,用户不耐烦刷新时。 |
| 500 |
Internal Server Error |
后端应用服务器内部错误(如PHP语法错误、Java异常)。 |
| 502 |
Bad Gateway |
Nginx从后端收到无效响应(后端进程崩溃、连接被拒等)。 |
| 503 |
Service Unavailable |
服务暂时不可用(常用于主动限流、维护页面)。 |
| 504 |
Gateway Timeout |
Nginx在等待后端响应时超时。 |
C. 排查决策流程图(简化版)
收到502/504告警
│
├─ 如果是502
│ ├─ 检查Nginx error.log
│ ├─ 检查后端进程/端口是否存活
│ ├─ 检查PHP-FPM/应用进程池是否耗尽
│ ├─ 检查系统资源(内存、文件描述符)
│ └─ 根据具体错误信息定位(连接拒绝、socket失效等)
│
└─ 如果是504
├─ 检查Nginx proxy_read_timeout等配置
├─ 分析access.log,确认上游响应时间(urt)
├─ 检查后端应用慢日志(PHP、Java)
├─ 检查数据库慢查询
├─ 检查外部API/依赖服务状态
└─ 检查网络状况(跨机房调用时)