本文介绍了如何排查Nginx遇到的502 Bad Gateway报错,强调首先查看错误日志定位问题。文章详细分析了上游服务卡死、Buffer溢出与Socket权限等常见问题,并提出了解决方案,如检查后端服务状态、内核日志确认OOM Killer、调整PHP-FPM配置等。
前几天下午刚准备喝口水,监控群里突然报警,好几个核心业务接口接连返回 502 Bad Gateway。打开浏览器一刷,页面直接挂上白底黑字的 nginx 默认报错页。
遇到 502 很多人的第一反应是把 Nginx 重启一遍。但我这几年线上排查下来,Nginx 本身几乎从来没有因为 502 崩溃过。502 的准确定义是网关错误(Bad Gateway),意思是 Nginx 作为反向代理节点活着,但它在找后面的上游服务(Upstream,比如 PHP-FPM、Node.js、Python Gunicorn 或者 Go 后端)要数据时,上游没有给出一个合法的 HTTP 响应。
盲目重启 Nginx 解决不了上游的问题。最稳妥的办法永远是先看 Nginx 的 error.log。日志里写得很具体,每次 502 后面基本都跟着一段原因。我把线上最常碰到的四种典型情况和排查过程整理出来,后续遇到类似报错可以直接对照看。
先定位 error.log,别靠猜
遇到 502 报错,排查的第一步始终是定位错误日志:
tail -n 30 /var/log/nginx/error.log
如果虚拟主机单独配置了日志目录,去对应的配置文件里找 error_log 路径。日志里一般会明确包含 [error] 级别的信息,格式大致如下:
2026/09/18 14:22:01 [error] 18422#18422: *398212 connect() failed (111: Connection refused) while connecting to upstream, client: 114.88.23.12, server: api.example.com, request: "GET /v1/user/info HTTP/1.1", upstream: "http://127.0.0.1:8080/v1/user/info", host: "api.example.com"
只要这一行出来了,排错范围直接缩小到 upstream 指向的地址。接下来针对常见的四种日志内容逐一排查。
坑一:上游服务被 OOM 杀掉或队列打满(Connection refused)
这是发生频率最高的一种场景。错误日志里出现 connect() failed (111: Connection refused),说明 Nginx 按照配置去连接后端的端口,但操作系统直接回绝了连接请求。
原因通常有两个:要么后端进程根本就没跑,要么并发过大把连接队列(backlog)全部占满了。
先检查后端端口是否在监听:
ss -tulpn | grep 8080
如果查不到监听信息,说明服务挂了。服务无缘无故退出,八成是触发了内核的内存溢出杀手(OOM Killer)。直接用 dmesg 查内核日志确认:
dmesg -T | grep -E -i "oom|killed process"
如果看到类似 Out of memory: Killed process 29811 (node) 的记录,说明后端吃光了物理内存被系统强行收割了。这时候光重启服务治标不治本,得查内存泄漏或者限制服务的堆内存上限。
如果是 PHP-FPM 架构,更常见的情况是进程池占满。PHP-FPM 默认的 pm = dynamic 模式下,pm.max_children 如果只配置了 5 个或 10 个,一旦突发流量过来,所有 worker 都在跑慢 SQL 或者等待外部接口,后续的新请求在 backlog 队列排队超时,Nginx 就会判定为 502。
去 PHP-FPM 的错误日志(一般在 /var/log/php-fpm.log 或 /var/log/php8.2-fpm.log)里能看到这样的警告:
WARNING: [pool www] server reached pm.max_children setting (10), consider raising it
临时解决可以适当调大 pm.max_children:
[www]
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 1000
这里设置 pm.max_requests = 1000 是为了防止 PHP 脚本长期运行存在微弱的内存泄漏,处理满一千次请求后自动重启 worker 释放内存。
坑二:Unix Socket 权限不匹配(13: Permission denied)
很多做 Linux 性能优化的文章都建议把本地反向代理从 TCP 回环(127.0.0.1:9000)改成 Unix Domain Socket(比如 unix:/run/php/php-fpm.sock),因为 Socket 免去了 TCP 协议栈握手开销,能提升不少吞吐量。
改完之后执行 reload,刷新网页立刻跳出 502。去翻 error.log,看到的内容往往是这样:
2026/09/20 10:15:32 [error] 12901#12901: *1201 connect() to unix:/run/php/php8.2-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.1.101, server: bbs.example.com, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:"
Permission denied 意思是 Nginx 的 worker 进程没有读取或写入该 sock 文件的权限。
排查 Socket 权限分两步。第一步看该 sock 文件的归属:
ls -l /run/php/php8.2-fpm.sock
输出大概是:
srw-rw---- 1 www-data www-data 0 Sep 20 10:12 /run/php/php8.2-fpm.sock
文件所有者是 www-data,权限是 660。如果你的 Nginx master 是 root 启动,但 worker 进程在 nginx.conf 顶层配置的是 user nginx;,那么属于 nginx 用户组的 worker 进程根本无权访问这个属于 www-data 组的文件。
打开 PHP-FPM 的池配置文件 /etc/php/8.2/fpm/pool.d/www.conf,找到 socket 权限相关字段,确保它和 Nginx 运行用户一致:
listen = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
或者直接将 nginx.conf 顶部的 user 统一改成与 fpm 相同的运行身份:
user www-data;
改完后重启 PHP-FPM,让它重新创建带有正确权限的 sock 文件,再 reload Nginx。
还有一个容易忽略的细节:systemd 的私有临时目录隔离。如果 sock 文件放在了 /tmp 下面,比如 unix:/tmp/php.sock,而 systemd 服务配置里开启了 PrivateTmp=true,那么 Nginx 和 PHP-FPM 会各自看到一个独立的虚拟 /tmp 目录,Nginx 永远找不到对应的文件。生产环境中,Socket 文件一定要统一放在 /run 或 /var/run 目录下。
坑三:响应头体积超限,Buffer 溢出(upstream sent too big header)
这个坑隐蔽性极强。系统日常运行完全正常,但只要特定用户登录、或者从单点登录系统(OAuth/CAS)跳回来时,页面瞬间爆 502。换一个没登录的无痕浏览器访问同一个页面,又一切正常。
查看 Nginx 的 error.log,抓到的关键报错如下:
2026/09/25 16:48:19 [error] 24102#24102: *874102 upstream sent too big header while reading response header from upstream, client: 220.181.108.85, server: sso.example.com, request: "GET /oauth/callback?code=xxx HTTP/1.1", upstream: "http://127.0.0.1:3000/oauth/callback?code=xxx"
字面意思很直白:上游服务返回的 HTTP 响应头太大,超出了 Nginx 为接收头部预留的缓冲区大小。
在前后端分离架构或者微服务认证中,后端经常会在响应头里塞很长的 Cookie,比如包含各种签名信息的 JWT、多个追踪标记,或者在灰度发布时塞入了一大堆调试 Header。
Nginx 接收后端响应时,默认的头部缓冲区大小非常保守。如果是 64 位系统,proxy_buffer_size 和 fastcgi_buffer_size 的默认值通常只有 4k 或 8k。一旦后端的响应头总长度超过这个临界值,Nginx 不会截断,而是直接粗暴地断开连接,向客户端返回 502。
解决办法是在反向代理配置中显式调大缓冲区。
如果是普通 HTTP 反向代理(proxy_pass):
location / {
proxy_pass http://127.0.0.1:3000;
# 调大专门用于读取上游第一部分响应头部的 buffer
proxy_buffer_size 16k;
# 调大后续读取主体内容的 buffer 数量和单块大小
proxy_buffers 4 32k;
# 在没有全部读完后端数据时,可以向客户端发送的最大 buffer 大小
proxy_busy_buffers_size 64k;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
如果是 FastCGI(PHP-FPM):
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# 调大 fastcgi 头部缓冲区
fastcgi_buffer_size 16k;
fastcgi_buffers 4 32k;
fastcgi_busy_buffers_size 64k;
}
配置后执行 nginx -t 检查语法,再执行 nginx -s reload,重新走一遍授权回调流程,报错立刻消失。
坑四:容器与微服务里的 IPv6 监听黑洞
最近两年很多基础镜像和新版本运行时默认开启了 IPv6 双栈支持。如果你把后端服务部署在 Docker 容器里,或者使用了 Node.js 18+、Go、Spring Boot,很可能会撞上这个隐式解析的坑。
配置反向代理时,很多人习惯写:
proxy_pass http://localhost:8080;
在很多 Linux 系统中,/etc/hosts 里 localhost 同时映射了两个地址:
127.0.0.1 localhost
::1 localhost
当 Nginx 看到 localhost 时,在很多系统解析库里会优先尝试 IPv6 的 [::1]:8080。然而,你的后端服务在启动时如果写的是 app.listen(8080, '127.0.0.1'),它实际上只绑定了 IPv4 的回环地址,并没有在 IPv6 接口上监听。
Nginx 先去连接 [::1]:8080,收到操作系统内核的 RST 拒绝包,报出 502。虽然部分 Nginx 机制会自动重试下一个地址,但在某些超时或高并发配置下,这种不必要的双重解析会直接导致大面积请求失败或延迟升高。
遇到这种偶发报错,最稳妥的做法有两个:
第一,反代配置里不要用容易产生歧义的 localhost 域名,直接指明 IPv4 地址:
proxy_pass http://127.0.0.1:8080;
第二,如果是集群 upstream 配置,显式绑定回环地址:
upstream backend_cluster {
server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;
keepalive 32;
}
加上 keepalive 32 还可以让 Nginx 与后端保持长连接,避免每次请求都在本地反复创建和销毁 TCP 套接字,不仅消除端口耗尽风险,还能降低请求握手耗时。
线上排查的兜底原则
处理 502 问题,核心在于理解反向代理的断连点在哪里。只要理清了数据流转,处理起来就会很有条理:
第一,Nginx 报 502 说明 Nginx 进程本身正常,不要慌着重启 Nginx,先看 error.log 的最后一屏内容。
第二,如果日志写着 Connection refused,去查后端进程是否存活、端口是否在监听、内存有没有触发 OOM。
第三,如果日志写着 Permission denied,去查 Unix Socket 文件的属主属组是否和 Nginx worker 运行用户一致。
第四,如果日志写着 upstream sent too big header,把 proxy_buffer_size 或 fastcgi_buffer_size 调大到 16k 或 32k。
第五,反代地址尽量显式指定具体 IP,避免依赖本地 hosts 域名解析。弄清楚了具体原因,再去针对性修改配置,线上服务才能长期维持稳定。
文章标题:Nginx 突发 502 Bad Gateway 报错?排查上游进程卡死、Buffer 溢出与 Socket 权限我踩过的几个坑
文章链接:https://www.llbbs.cn/jishujaocheng/216.html
本站文章均为原创,未经授权请勿用于任何商业用途
评论一下?