服务器出现502 Bad Gateway错误怎么办?Nginx与后端服务排查指南

访问网站时突然冒出一个"502 Bad Gateway",是运维和站长最常遇到的报错之一。它跟 404、403 不一样:404 是找不到页面,403 是没权限,而 502 的含义很明确——Nginx(作为网关/反向代理)成功拿到了用户请求,但去请求后端的那个"上游"服务时失败了。换句话说,502 几乎总是发生在 Nginx 和它背后的应用之间。前端没问题,问题在后端。

这篇文章聚焦一个场景:Nginx 反向代理(或 FastCGI)后面的后端服务,出现 502 时怎么一步步定位。我们会讲清 502 到底是谁返回的、Nginx 里哪几个配置会决定它、常见原因有哪几类(后端进程崩了、上游地址端口写错、PHP-FPM 没起、socket 权限不对、SELinux 拦截、上游主动拒绝、超时),然后给出一套"看 error.log → curl 后端 → 查进程和端口 → 看后端自身日志"的排查流程。读完你应该能不再靠"重启 Nginx 碰运气",而是精确定位到是后端哪一环出了问题。

先记住一句话:502 是 Nginx 报的,但根因在 Nginx 背后。所以排查的起点永远是 Nginx 的 error.log,那里会写清楚它去连谁、为什么失败。

一、502 到底是谁返回的

要理解 502,先理解 Nginx 的角色。当 Nginx 做反向代理时,它接受客户端请求,然后自己再作为客户端去访问上游(upstream),比如:

客户端 → Nginx (proxy_pass) → 上游应用 (Tomcat / PHP-FPM / Node / Gunicorn) → 数据库
         ↑ 502 在这条链路上产生

Nginx 只有在"未能从上游拿到一个有效的、合法的响应"时才返回 502。具体包括:

  • 连不上上游(Connection refused)——上游没监听、进程崩了、端口错。
  • 连接被上游主动关闭(Connection reset by peer)——上游处理一半崩了。
  • 上游返回的响应格式非法(upstream sent invalid header 等)。
  • FastCGI 场景下,PHP-FPM 进程池耗尽、脚本执行崩溃。

对应地,Nginx 的 error.log 里会留下非常具体的描述。所以第一步一定是看错误日志,而不是猜。

二、第一步:看 Nginx 错误日志

# 一般路径,具体以配置里的 error_log 为准
tail -n 100 /var/log/nginx/error.log

# 只看 502 相关的
grep -i '502\|upstream\|connect() failed\|no live upstreams' /var/log/nginx/error.log | tail -50

# 实时观察(复现问题时用)
tail -f /var/log/nginx/error.log

常见报错和它的含义对照:

error.log 报错 含义 大概率原因
connect() failed (111: Connection refused) 上游拒绝连接 后端没启动、端口错、只听 127.0.0.1
no live upstreams 所有上游都不可用 后端全部宕或者健康检查全失败
upstream timed out 等后端超时(常伴随 504) 后端慢或卡死
upstream sent invalid header 上游返回非法响应 后端协议错、端口连到了错误服务
recv() failed (104: Connection reset by peer) 上游主动断开 后端崩溃、进程被杀
Permission denied while connecting to upstream 权限问题 SELinux、socket 权限

看到具体报错,方向就明确了。比如 Connection refused 就是"没人听这个端口",Permission denied 就是"权限/SELinux",Connection reset 就是"后端处理时崩了"。

同时看配置里 Nginx 到底把请求转给了谁:

# 找到站点配置,看 proxy_pass 指向哪里
grep -rn 'proxy_pass\|fastcgi_pass\|upstream' /etc/nginx/
nginx -T | grep -A5 'proxy_pass\|fastcgi_pass'   # 打印完整生效配置

三、第二步:手动模拟 Nginx 去访问后端

既然 Nginx 是"客户端",我们就用 curl 手动去连一次,直接从浏览器的角度验证后端。

# 假设 proxy_pass 指向 http://127.0.0.1:8080
curl -v http://127.0.0.1:8080/
curl -v -m 5 http://127.0.0.1:8080/health     # -m 限制超时,避免卡死

# FastCGI/PHP 场景,Apache/Nginx 常见端口是 9000
nc -zv 127.0.0.1 9000

# 如果后端在别的机器上
curl -v http://10.0.0.5:8080/
nc -zv 10.0.0.5 8080

# 带上 Host 头(虚拟主机场景)
curl -v -H 'Host: www.example.com' http://127.0.0.1:8080/

结果判读:

  • Connection refused:后端没监听该端口。跳到第四步查进程和端口。
  • curl: (7) Failed to connect:同上,网络或监听问题。
  • 能返回内容但格式错乱:proxy_pass 端口指错了,连到了别的服务。
  • 返回 200 但通过 Nginx 还是 502:可能是权限/SELinux,或者上游地址和手工测的不一致。

这一步的价值在于把 Nginx 和后端分离开:如果手工 curl 后端正常、走 Nginx 却 502,问题就在 Nginx 配置或权限层;如果手工 curl 也连不上,问题在 Nginx 背后,继续往下查。

四、第三步:查后端进程与端口

后端起没起、在没在监听,是 502 最高频的原因。

# 看端口是否在监听(最关键)
ss -lntp | grep ':8080'
ss -lntp | grep ':9000'      # PHP-FPM

# 注意监听地址
# LISTEN 0 511 127.0.0.1:9000  —— 只接受本机
# LISTEN 0 511 0.0.0.0:9000    —— 接受所有来源

# 看后端服务状态
systemctl status php-fpm
systemctl status tomcat
systemctl status myapp

# 看进程在不在
ps aux | grep -E 'php-fpm|java|node|gunicorn' | grep -v grep

# 看谁占了(或没占)这个端口
lsof -i :8080

这一层的三种典型情况:

  1. 端口没监听、进程不存在:后端服务挂了,去查它自己的日志找崩溃原因(不要直接 restart,先看为什么挂)。
  2. 端口在听但就在 127.0.0.1:如果 Nginx 在另一台机器上,就连不上;即使同机,如果配置里写了主机对外 IP 也会对不上。改后端监听地址或改 Nginx 指向。
  3. 端口被别的进程占了:lsof -i :8080 能看出来,可能是端口冲突。

PHP-FPM 专项排查

PHP 站点 502 大多是 PHP-FPM 的问题,几个高频原因:

# 看 PHP-FPM 是否在跑、状态是否正常
systemctl status php-fpm
# 看 FPM 自己的日志(路径以池配置为准)
tail -n 100 /var/log/php-fpm/error.log
tail -n 100 /var/log/php-fpm/www-error.log

# 看 FPM 进程池有没有耗尽的迹象
ps aux | grep php-fpm | wc -l
# 看 FPM 状态页(需在 pool 配置里开启 pm.status_path)
curl http://127.0.0.1/status

PHP-FPM 报 502 的常见原因:

  • 进程池打满:pm.max_children 设得太小,请求一多就 server reached pm.max_children setting, consider raising it,多余请求拿不到 worker,Nginx 就 502。
  • 脚本执行超时/内存超限被杀:PHP 脚本 OOM 或被 max_execution_time 杀掉,FPM worker 崩溃。
  • socket 权限:如果用的是 Unix socket(fastcgi_pass unix:/run/php-fpm/www.sock),socket 文件权限不对、或 Nginx 用户不在 FPM 组里,会连不上。
# 检查 socket 文件权限
ls -l /run/php-fpm/www.sock
# 常见做法:让 nginx 用户能访问 socket
# pool 配置里设置 listen.owner / listen.group / listen.mode
grep -E 'listen' /etc/php-fpm.d/www.conf     # RHEL 系
grep -E 'listen' /etc/php/*/fpm/pool.d/www.conf   # Debian/Ubuntu

五、第四步:SELinux 与权限

在 RHEL/CentOS/Rocky 上,SELinux 是 502 的一个经典隐藏原因。表现是:权限看着都对(文件属主、rwx 都没问题),但 Nginx 就是连不上后端。

# 看 SELinux 状态
getenforce                 # Enforcing / Permissive / Disabled
sestatus

# 看是不是 SELinux 拦截了 Nginx 连后端
sudo ausearch -m avc -ts recent | grep nginx 2>/dev/null
sudo grep -i 'denied' /var/log/audit/audit.log | grep nginx | tail

# Nginx 连后端的两种开关
# 允许 Nginx 发起网络连接(默认应是 on)
getsebool httpd_can_network_connect
setsebool -P httpd_can_network_connect on     # 临时验证可先用带 -P 永久

# 允许 Nginx 连数据库
getsebool httpd_can_network_connect_db

setsebool -P 会永久修改策略。如果只是临时验证,可以先不加 -P(重启后失效)。更规范的做法是用 semanage 给非标准端口打标签(如让 Nginx 能连 8080):

# 允许 httpd 连非标准端口 8080(RHEL 系)
semanage port -a -t http_port_t -p tcp 8080
semanage port -l | grep http_port_t

风险提示:直接把 SELinux 设成 Disabled 或 setenforce 0 是掩盖问题,不是解决问题,也降低了系统安全性,生产环境不建议。应先定位 avc 拒绝记录,再针对性放行。

六、第五步:上游主动拒绝与上游崩溃

如果 curl 127.0.0.1:8080 能连上、但一发起真实请求就 502,或者 error.log 里是 Connection reset by peer、upstream prematurely closed,说明后端在处理请求的过程中崩了。这时要看后端自己的日志:

# Java 应用
tail -n 200 /var/log/myapp/catalina.out        # Tomcat
journalctl -u tomcat -n 100 --no-pager

# Node / Python
journalctl -u myapp -f
tail -f /var/log/myapp/app.log

# 搜后端崩溃痕迹
grep -iE 'crash|exception|segfault|killed|oom|fatal' /var/log/myapp/*.log | tail -50

后端崩溃的常见原因:内存不够被 OOM 杀掉、代码异常未捕获、数据库连接泄漏导致连接池耗尽、某个请求触发了 bug。

另外注意连接被上游拒绝的另一种情况:后端有自己的连接限流(如 Tomcat 的 maxThreads、Gunicorn 的 worker 数),并发一高,超出部分的连接被拒,Nginx 侧看到的就是 502。这时要调后端的并发配置,或者给 Nginx 配上重试与多上游。

七、Nginx 侧的配置要点

排查完根因后,Nginx 侧有几个配置值得检查和优化,能减少 502 的概率或加快定位。

upstream backend {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:8081 backup;        # 备用上游
    keepalive 32;                         # 保持长连接,减少握手
}

server {
    listen 80;
    server_name www.example.com;

    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";   # 配合 keepalive 使用
        proxy_next_upstream error timeout http_502;  # 上游出错时换下一个
        proxy_connect_timeout 5s;         # 连后端超时
        proxy_read_timeout 60s;           # 读后端响应超时
        proxy_send_timeout 60s;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

几个要点:

  • max_fails 和 fail_timeout:某个上游连续失败 N 次后,在 fail_timeout 内被标记为不可用,避免请求一直打到坏的上游。配置得当可以减少用户侧感知到的 502。
  • proxy_next_upstream:出现 error/timeout/502 时自动换下一个上游。但要注意,如果是 POST 等非幂等请求,重试可能造成重复提交,需谨慎。
  • keepalive:与应用保持长连接,减少连接建立开销,高并发下能明显降低 502 概率。

改完配置用 nginx -t 校验语法,再 nginx -s reload 平滑重载(不要直接 restart,会中断现有连接)。

nginx -t && nginx -s reload

八、一套可复用的排查清单

步骤 命令 看什么
1. 看 Nginx 错误日志 tail /var/log/nginx/error.log connect failed / reset / timeout
2. 确认上游地址 nginx -T \| grep proxy_pass 指向的 IP:端口是否正确
3. 手工 curl 后端 curl -v http://127.0.0.1:8080/ 后端是否可用
4. 查端口监听 ss -lntp \| grep :8080 是否监听、监听地址
5. 查进程 systemctl status php-fpm 后端进程是否存活
6. 查后端日志 tail /var/log/myapp/*.log 后端崩溃/池满/异常
7. 查 SELinux getenforce、ausearch -m avc 是否被策略拦截
8. 查 socket 权限 ls -l /run/php-fpm/*.sock Unix socket 权限
9. 校验并重载 nginx -t && nginx -s reload 配置是否生效

绝大多数 502 在前五步就能定位。核心思路始终是:先看 Nginx 说它连不上谁、为什么,再手工复现,最后查后端。

常见问题

Q:重启 Nginx 后 502 就好了一阵,过会儿又出现,为什么?
说明根因不在 Nginx。重启只是暂时让连接重建,后端的问题(进程池满、内存泄漏、后端崩溃)没解决,过段时间还会复发。要看后端日志找根因,而不是反复重启 Nginx。

Q:HTTP 请求 502,HTTPS 却正常(或反过来),可能是什么?
检查两套 server 块的 proxy_pass 是否一致,可能只改了其中一个;也可能是 SSL 证书或握手的独立问题。分别用 curl -I http://... 和 curl -I https://... 对比。

Q:no live upstreams 是什么意思?
表示 Nginx 认为所有上游都不可用(都达到了 max_fails 被摘除,或者全部宕机)。先确认后端进程是否都活着,再看健康检查/max_fails 是否配置过严导致误摘。

Q:改了 proxy_pass 端口还是 502?
确认改的是生效的配置文件(nginx -T 看最终结果),并确保 nginx -s reload 成功执行。也检查是否有 include 的多个站点配置互相冲突。

Q:是 PHP 站点,502 很频繁,优先查什么?
先看 PHP-FPM 的 pm.max_children 是否过小(日志里会有 reached pm.max_children),再确认 socket 权限、request_terminate_timeout、以及 PHP 脚本是否内存超限被 kill。

总结

502 的本质是:Nginx 作为网关去请求上游后端失败。它由 Nginx 返回,但根因在上游。排查必须从 Nginx 的 error.log 开始,因为那里写明了失败的具体原因。

最高频的几类原因是:后端进程未启动、上游端口/地址配错、监听地址只在 127.0.0.1、PHP-FPM 进程池满、Unix socket 权限、SELinux 拦截、后端处理时崩溃。

排查顺序固定:看 error.log → 确认 proxy_pass 指向 → 手工 curl 后端 → 查进程和端口 → 看后端自身日志 → 查 SELinux 与权限。多数问题五步内定位。

不要用"重启 Nginx 掩盖问题",也尽量不要靠关闭 SELinux 绕过权限。正确做法是定位到后端的具体环节,修根因,再配合 max_fails、proxy_next_upstream、keepalive 等配置提升健壮性。