更新于
Nginx 502 Bad Gateway 怎么解决:按 error.log 找到上游原因,在 Ubuntu 上一步步修好
简短回答:Nginx 返回 502 Bad Gateway,是因为它转发到的上游(你的应用、PHP-FPM、Gunicorn、容器)拒绝了连接、没回响应就断开,或者回了 Nginx 不接受的内容。Nginx 本身通常没坏。请求失败后马上执行 sudo tail -n 50 /var/log/nginx/error.log:那一刻写下的报错会指出原因,upstream: 字段就是 Nginx 实际去连的地址。
本文写给在 Ubuntu 22.04 / 24.04 上用 Nginx 做反向代理的开发者。先给一张 Nginx 真实报错对照表,再按顺序走一遍 502 bad gateway 的解决步骤。排查步骤里的命令只查看状态,会改动服务器的操作统一放在最后。只在你有权限的服务器上执行。
502 bad gateway nginx 是什么意思
Nginx 就是那个 “gateway”(网关)。502 表示 Nginx 收下了访问者的请求、转给了后面的服务,却没拿到能用的响应。常见原因:
- 目标地址上没有程序在监听:
proxy_pass/fastcgi_pass指向的应用停了、崩了、还在启动,或者换了端口、换了 socket。 - Nginx 没权限连:Unix socket 的属主或权限不对。
- 应用把请求丢了:处理中崩溃、被杀(内存不足、worker 超时),或者正好在重启。
- 应用回了 Nginx 不接受的内容:响应头格式错误或太大,或者协议对不上(HTTP 和 HTTPS 混了)。
如果应用有响应、只是太慢,Nginx 返回的是 504 Gateway Timeout。先分清 502 还是 504,排查范围就小了一半。
对照 error.log 报错原文
找到和失败请求同一时间的那一行。第一列是 Nginx 实际写出的原文;你服务器上的地址、socket 路径、PHP 版本号会不一样。每行末尾还有 client: ..., server: ..., request: "...", upstream: "...", host: "..." 这些上下文。
| error.log 里写的 | 状态码 | 可能原因 | 下一步 |
|---|---|---|---|
connect() failed (111: Connection refused) while connecting to upstream | 502 | upstream: 地址上没有程序在监听:应用停了、崩了、还在启动,或者换了端口 | 第 4 步、第 5 步 |
connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory) while connecting to upstream | 502 | socket 文件不存在:PHP-FPM(或应用)没在跑,或者 Nginx 指向了另一个版本的 socket | ls -l /run/php/;第 5 步 |
connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied) while connecting to upstream | 502 | Nginx 的运行用户(Ubuntu 上是 www-data)打不开这个 socket | 第 7 步 |
connect() to unix:/run/php/php8.3-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream | 502 | socket 的等待队列满了:worker 全忙,排队也排满了 | 查 PHP-FPM 日志里的 max_children;第 8 步 |
upstream prematurely closed connection while reading response header from upstream | 502 | 应用接了请求,没回响应就断开:崩溃、worker 被杀、应用服务器自己超时 | 第 8 步 |
recv() failed (104: Connection reset by peer) while reading response header from upstream | 502 | 上游重置了连接:进程崩溃或重启、worker 被终止,或者复用的长连接已被对方关掉 | 第 8 步;长连接 |
upstream prematurely closed FastCGI stdout while reading response header from upstream | 502 | PHP-FPM 的 worker 还没输出就结束了(致命错误、崩溃或被杀) | PHP-FPM 日志和 PHP 错误日志 |
upstream sent invalid header: "..." while reading response header from upstream | 502 | 应用发来的某行响应头格式不对 | 无效响应 |
upstream sent too big header while reading response header from upstream | 502 | 响应头(常见是很大的 Set-Cookie)超出了 proxy_buffer_size(PHP 是 fastcgi_buffer_size) | 缓冲区 |
SSL_do_handshake() failed (SSL: error:...:wrong version number) while SSL handshaking to upstream | 502 | proxy_pass https:// 指向了一个只讲明文 HTTP 的端口 | 这个上游改用 http://,或者指向它的 TLS 端口 |
no live upstreams while connecting to upstream | 502 | upstream {} 里所有服务器都因为之前的错误被标成不可用 | 往上翻:更早的报错才说明每台为什么失败 |
connect() failed (113: No route to host) while connecting to upstream | 502 | 上游 IP 不可达:IP 写错、主机或容器已不在,或被防火墙拒绝 | ip route get UPSTREAM_IP;Docker |
upstream timed out (110: Connection timed out) while reading response header from upstream | 504 | 应用在 proxy_read_timeout(默认 60 秒)内没回应 | 看应用日志里的慢请求;超时 |
两个看着像、其实不是 502 的:
host not found in upstream "app:3000"是 Nginx 加载配置时以[emerg]级别写的,Nginx 会拒绝启动或重载,访问者看到的是连接失败,不是 502。sudo nginx -t就能看到。- TCP 地址上的
connect() to 127.0.0.1:3000 failed (13: Permission denied),是 RHEL、Rocky、Alma 上典型的 SELinux 拦截。Ubuntu 不用 SELinux,见第 7 步里关于 AppArmor 的说明。
Nginx 写的是 invalid header;“The proxy server received an invalid response from an upstream server” 是 Apache 的原文,不是 Nginx
Nginx 不会写 “upstream sent invalid response” 这句原话。上游回的内容有问题时,错误日志里是下面几种之一,最终都返回 502:
upstream sent invalid header: "...":某行响应头 Nginx 解析不了,比如头名里有非法字符、少了冒号。Nginx 1.24(Ubuntu 24.04)会把出错那行的开头引出来;Nginx 1.18(Ubuntu 22.04)只写upstream sent invalid header。upstream sent too big header:状态行加响应头超过了缓冲区,常见是越变越大的会话 Cookie。upstream sent invalid "Content-Length" header: "..."、upstream sent "Content-Length" and "Transfer-Encoding" headers at the same time:应用或框架中间件手工拼响应头拼错了。
想看应用到底回了什么,绕过 Nginx 直接看原始响应头(第 6 步):
curl -sS -o /dev/null -D - --max-time 10 -H 'Host: example.com' http://127.0.0.1:3000/PATH
如果你看到的是 “The proxy server received an invalid response from an upstream server”,那是 Apache mod_proxy 的错误页,不是 Nginx 的,去看 Apache 的错误日志(Ubuntu 上是 /var/log/apache2/error.log)。
502 Bad Gateway 解决步骤(Ubuntu)
把 example.com、YOUR_APP、端口 3000 和 socket 路径换成你自己的。Ubuntu 22.04 自带 PHP 8.1,24.04 自带 PHP 8.3,php8.3-fpm 按实际版本改。
第 1 步:确认 502 是你自己的 Nginx 返回的
curl -sS -o /dev/null -D - https://example.com/ | grep -i -E '^(HTTP|server|cf-ray)'
curl -sS -o /dev/null -w '%{http_code}\n' --resolve example.com:443:127.0.0.1 https://example.com/
第一条走 DNS 和 CDN。响应头里有 server: cloudflare 或 cf-ray,说明是 CDN 回的;CDN 的 502 页面表示它没从你的服务器拿到正常响应。第二条在服务器上执行,把同样的请求直接发给本机 Nginx、跳过 CDN(站点没有 TLS 就用 http:// 和 80 端口)。这条也是 502,就继续往下查;这条是 200,问题出在 CDN 和你服务器之间,不在 Nginx 后面。
Nginx 自己的错误页显示 502 Bad Gateway,底部有一行类似 nginx/1.24.0 (Ubuntu)。那只是版本号,升级 Nginx 解决不了 502。
第 2 步:确认 Nginx 在运行、配置没错
systemctl status nginx --no-pager
sudo nginx -t
看到 active (running),加上 syntax is ok 和 test is successful,说明 Nginx 本身是好的,502 出在上游。如果 nginx -t 报错,正在跑的 Nginx 可能还在用旧配置;先修好它指出的错误,再考虑重载。
第 3 步:看 502 发生那一刻的错误日志
sudo tail -n 50 /var/log/nginx/error.log
sudo grep -E 'upstream|connect\(\)' /var/log/nginx/error.log | tail -n 20
sudo awk '$9 == 502' /var/log/nginx/access.log | tail -n 5
某个 server 块可能有自己的日志文件。找出它,以及每个 location 转发到哪里:
sudo nginx -T 2>/dev/null | grep -n -E 'error_log|access_log|proxy_pass|fastcgi_pass|uwsgi_pass|upstream'
nginx -T 会打印完整的已加载配置,里面可能有凭证,不要贴到公开渠道。502 时有时无的话,在一个终端里开着 sudo tail -f /var/log/nginx/error.log,在另一个终端里复现请求。
完整的一行长这样:
2026/10/08 14:03:12 [error] 1234#1234: *5678 connect() failed (111: Connection refused) while connecting to upstream, client: 203.0.113.10, server: example.com, request: "GET /api/orders HTTP/1.1", upstream: "http://127.0.0.1:3000/api/orders", host: "example.com"
upstream:是 Nginx 实际去连的地址。拿它和应用真正监听的地址对比,很多 502 到这里就解决了。request:能看出是所有路径都失败,还是只有某个接口。- 时间是服务器本地时间。
第 4 步:看上游服务的状态和日志
systemd 管理的应用(Node、Gunicorn、uWSGI、Puma 等):
systemctl status YOUR_APP --no-pager
sudo journalctl -u YOUR_APP --since "30 min ago" --no-pager | tail -n 50
PHP-FPM:
systemctl status php8.3-fpm --no-pager
sudo journalctl -u php8.3-fpm --since "30 min ago" --no-pager | tail -n 30
sudo tail -n 50 /var/log/php8.3-fpm.log
容器:
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker logs --since 30m --timestamps YOUR_CONTAINER 2>&1 | tail -n 50
看服务是不是停了、在重启循环里(activating (auto-restart)、Restarting (1)),或者在 502 那个时间点记录了崩溃。容器反复退出的话,另有一篇:Docker 退出码是什么意思?容器秒退、立即退出时看 0、1、125、137、139、143、255。
第 5 步:确认 Nginx 去连的地方有程序在监听
sudo ss -ltnp
sudo ss -lxp | grep -E 'php|gunicorn|uwsgi|\.sock'
ls -l /run/php/
ss -ltnp 列出正在监听的 TCP 端口和占用它的进程,ss -lxp 列出 Unix socket。拿它们和第 3 步的 upstream: 对比:
- 应用在
3001,Nginx 发往3000:端口对不上,常见于发布后,或者PORT环境变量改了。 proxy_pass http://localhost:3000,应用只监听127.0.0.1:Nginx 可能把localhost解析成127.0.0.1和[::1]两个地址,IPv6 那次会被拒绝(日志里能看到upstream: "http://[::1]:3000/...")。proxy_pass里直接写127.0.0.1就没有歧义了。- 存在
/run/php/php8.1-fpm.sock,Nginx 却用php8.3-fpm.sock:升级 PHP 后站点配置里还是旧路径。
第 6 步:绕过 Nginx,直接请求上游
curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' --max-time 10 -H 'Host: example.com' http://127.0.0.1:3000/
curl -sS -o /dev/null -w '%{http_code}\n' --max-time 10 --unix-socket /run/gunicorn.sock http://localhost/
路径选一个可以安全请求的。第二条用于挂在 Unix socket 上的 HTTP 应用(Gunicorn、Puma、HTTP 模式的 uWSGI)。
- 这里也是 Connection refused:应用没在提供服务,回到第 4 步。
- 这里 200,经过 Nginx 却是 502:把你测的地址和
upstream:再对一遍;查 socket 权限(第 7 步)和响应头(无效响应)。 - 卡住很多秒才返回:应用慢,经过 Nginx 一般会变成 504。
PHP-FPM 说的是 FastCGI,不是 HTTP,curl 测不了。如果 pool 配置里开了 ping.path,可以用 libfcgi-bin 包里的 cgi-fcgi 请求它:
SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET cgi-fcgi -bind -connect /run/php/php8.3-fpm.sock
没开的话,服务在运行、www-data 能打开 socket 文件(第 7 步),就是实用的判断标准。
第 7 步:检查 Unix socket 权限
Ubuntu 上 Nginx 的 worker 以 www-data 运行。它需要对 socket 有写权限,并且对 socket 上面每一级目录都有执行(进入)权限。
ps -o user= -C nginx | sort | uniq -c
ls -l /run/php/php8.3-fpm.sock
sudo grep -E '^\s*(user|group|listen(\.owner|\.group|\.mode)?)\s*=' /etc/php/8.3/fpm/pool.d/*.conf
namei -l /run/gunicorn.sock
- 默认的 pool 配置是
listen.owner = www-data、listen.group = www-data、listen.mode = 0660。从别的服务器拷过来的 pool,或者为了换运行用户改过的 pool,常在这里出错。 namei -l会列出路径上每一级目录的属主和权限。socket 放在/home/USER/...下面时,如果 home 目录不允许www-data进入,就会失败。- 应用的 systemd 单元开了
PrivateTmp=yes(systemctl show -p PrivateTmp YOUR_APP查看)时,它在/tmp里建的 socket,Nginx 是看不到的,日志里会是(2: No such file or directory)。socket 放到/run/下面。 - AppArmor:Ubuntu 用的是 AppArmor,不是 SELinux;Ubuntu 软件源里的 Nginx 默认没有用 AppArmor 配置文件限制它。除非有人加过,
sudo aa-status | grep -i nginx不会有输出。没有配置文件时,Ubuntu 上的(13: Permission denied)就是文件权限问题。
第 8 步:查应用是不是被杀了或者超时了
upstream prematurely closed connection 和 Connection reset by peer,通常说明处理这个请求的 worker 没了。在 502 发生的同一分钟里查:
sudo journalctl -k --since "1 hour ago" --no-pager | grep -i -E 'out of memory|killed process'
sudo journalctl -u YOUR_APP --since "1 hour ago" --no-pager | grep -i -E 'worker timeout|killed|signal|error'
sudo grep -i -E 'timed out|max_children|exited on signal' /var/log/php8.3-fpm.log | tail -n 20
- 内核 OOM Killer:
Out of memory: Killed process ...,后面是你的应用或某个 worker。加内存不一定就是答案,先找出是什么涨上去了。容器场景见 Docker 退出码 137 与 OOMKilled。 - Gunicorn:
[CRITICAL] WORKER TIMEOUT表示某个 sync worker 跑得比--timeout(默认 30 秒)还久,被 Gunicorn 杀掉了,Nginx 看到的就是连接被断开。调大 Nginx 的超时没用,是应用服务器先把请求掐了。 - PHP-FPM:
execution timed out ... terminating来自request_terminate_timeout。server reached pm.max_children表示请求在排队;队列也满了,Nginx 就会记(11: Resource temporarily unavailable)。
Docker 里,127.0.0.1 不是你的应用
在 Nginx 容器里写 proxy_pass http://127.0.0.1:3000;,指向的是 Nginx 容器自己,结果就是 (111: Connection refused)。常见改法如下,每一项都是变更,先评估再做:
- 在同一个 Compose 网络里,转发到服务名:
proxy_pass http://app:3000;。 - 应用在自己的容器里要监听
0.0.0.0,不能只监听127.0.0.1。 - Nginx 装在宿主机上时,把应用端口发布到本机回环地址(比如
127.0.0.1:3000:3000),再转发到这个端口。
Nginx 在加载配置时解析 proxy_pass 里的主机名。应用容器重建后换了 IP,Nginx 可能还在连旧 IP,日志里是 (113: No route to host) 或 (111: Connection refused),直到重载为止。官方 Nginx 镜像把错误日志输出到 stderr:docker logs --since 15m YOUR_NGINX_CONTAINER 2>&1 | grep -i upstream。
超时、缓冲区和长连接:日志指向这里时再改
超时对应的是 504,不是 502。 proxy_connect_timeout、proxy_send_timeout、proxy_read_timeout 默认都是 60 秒。只给本来就慢的那个 location(比如导出报表)调大 proxy_read_timeout,并且先弄清它为什么慢。
upstream sent too big header 要调大缓冲区。 proxy_buffer_size 默认是一个内存页(x86_64 上是 4k)。和 proxy_buffers 一起调:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
}
只把 proxy_buffer_size 调到 16k,nginx -t 会报 "proxy_busy_buffers_size" must be less than the size of all "proxy_buffers" minus one buffer,因为 busy buffers 的默认值会跟着变大。PHP-FPM 对应的是 fastcgi_buffer_size 和 fastcgi_buffers。响应头一直在变大的话,那个 Cookie 或响应头本身也要修。
用了上游长连接,偶尔出现 502。 如果 upstream {} 里配了 keepalive,Nginx 会复用空闲连接。应用比 Nginx 更早关掉空闲连接时,请求可能正好落在一条刚被关掉的连接上,Nginx 就记 upstream prematurely closed connection 或 (104: Connection reset by peer)。比如 Node.js 默认 5 秒后关闭空闲的 keep-alive 连接(server.keepAliveTimeout)。让应用的空闲超时长于 upstream 块里的 keepalive_timeout(默认 60 秒),或者把后者调小。
安全地动手修复
拉起停掉的应用、改 proxy_pass 或 socket 路径、修 socket 属主、调缓冲区,都属于变更:
- 先备份文件,比如
sudo cp /etc/nginx/sites-available/example.com /root/example.com.bak。 - 只改日志指向的那一处。
sudo nginx -t,通过后sudo systemctl reload nginx(reload 不断开现有连接,restart 会断开)。- 用原来失败的请求再试一次,同时看着
sudo tail -f /var/log/nginx/error.log。 - 没有好转就把备份放回去,再
nginx -t、重载。
不要为了保险把所有超时都调大,也不要把 socket 权限放开到 0666;日志指向哪个原因,就修哪个。
说明性示例
说明性示例,不是真实客户案例:服务器从 Ubuntu 22.04 升级到 24.04(PHP 8.1 变成 8.3)后,所有 PHP 页面都返回 502。错误日志里是 connect() to unix:/run/php/php8.1-fpm.sock failed (2: No such file or directory) while connecting to upstream。ls -l /run/php/ 只有 php8.3-fpm.sock,systemctl status php8.1-fpm 提示找不到这个服务。这支持“站点配置里还是旧 socket 路径”的判断。怎么修由人来定:确认站点需要的 PHP 扩展在 8.3 下都装好了,再把 fastcgi_pass 改成新 socket,nginx -t、重载、再测一次。
AI 排查提示词
这个网站返回 502 Bad Gateway。请先只做检查、不做任何改动:找到正在使用的 Nginx error_log,列出最近 15 分钟和 upstream 有关的报错,把 upstream: 地址和实际监听情况(端口、Unix socket、容器)对比,查看上游服务的状态和日志,并在服务器上直接请求上游。区分已证实的事实和推测。不要重启服务或修改配置;如需变更,先给出最小步骤、风险、验证方法和回退方案,等我同意。
参考:Nginx proxy 模块、Nginx upstream 模块、Nginx fastcgi 模块。
如果整站是半夜才打不开,502 可能只是其中一个症状,先看 网站半夜打不开 时怎么做前 15 分钟分流。如果同一时间还有 可疑 SSH 登录,先做只读排查并保留现场,不要为了消掉 502 去删日志或重启。
相关指南
- 凌晨两点网站打不开:一个人值班时的前 15 分钟排查顺序
- 服务器被入侵了吗?可疑登录与异常进程的只读排查
- Docker 退出码是什么意思?容器秒退、立即退出时看 0、1、125、137、139、143、255
- Docker 退出码 137 与 OOMKilled:是内存不够,还是被别的东西杀掉了?
常见问题
Nginx 报 502 Bad Gateway 是什么原因,怎么解决?
502 表示 Nginx 收下了请求、转给上游(你的应用、PHP-FPM、Gunicorn 或容器),却没拿到能用的响应:上游拒绝了连接、没回响应就断开,或者回了 Nginx 不接受的内容。Nginx 本身通常没坏。请求失败后马上执行 sudo tail -n 50 /var/log/nginx/error.log,那一刻写下的报错会指出原因,upstream: 字段就是 Nginx 实际去连的地址。
「The proxy server received an invalid response from an upstream server」是 Nginx 的报错吗?
不是。这句是 Apache mod_proxy 的错误页,要去看 Apache 的错误日志(Ubuntu 上是 /var/log/apache2/error.log)。Nginx 也不会写「upstream sent invalid response」这句原话:上游回的内容有问题时,error.log 里是 upstream sent invalid header、upstream sent too big header 或和 Content-Length 有关的报错,最终都返回 502。
error.log 里是 connect() failed (111: Connection refused) while connecting to upstream,怎么办?
这表示 upstream: 那个地址上没有程序在监听:应用停了、崩了、还在启动,或者换了端口。先用 systemctl status 或 docker ps -a 看上游服务的状态和日志,再用 sudo ss -ltnp 把实际监听的端口和 upstream: 对一遍。Nginx 跑在容器里时,proxy_pass http://127.0.0.1:3000 指向的是 Nginx 容器自己;在同一个 Compose 网络里,常见改法是转发到服务名。
502 和 504 有什么区别?502 要不要调大超时?
502 是上游拒绝连接、没回响应就断开,或者回了无效内容;504 Gateway Timeout 是应用有响应但太慢,没在 proxy_read_timeout(默认 60 秒)内回应。超时对应的是 504,所以 502 时调大 Nginx 超时一般没用;比如 Gunicorn 的 WORKER TIMEOUT,是应用服务器先把请求掐了。只给本来就慢的 location 调大超时,并先弄清它为什么慢。
需要帮助理解排查结果?
OpsMate(itops.sh)把 SSH 终端和 AI 助手放在同一页:上面的命令可以自己手敲,也可以用一句话描述 502,再拿 AI 的回答和原始输出对照。发给 AI 的命令输出会在云端处理,请先去掉密钥等敏感信息。免费版每月 500 次 AI 调用,服务器数量不限。
免费开始 本地凭证桌面端