502 是 Nginx 作为反向代理时最常见的错误之一。它不代表 Nginx 本身崩了,而是 Nginx 没能从上游服务拿到合法响应。这篇文章按"从外到内逐层排除"的顺序,把排查路径拆成可执行的动作,每条命令都能直接照抄。
适用场景
适合已经在用 Nginx 做反向代理(代理 Node.js、Python、Java、PHP-FPM、Go 等上游服务),访问页面时看到 502 或间歇性 502 的开发者与运维人员。本文覆盖四类最常见成因:上游服务没起来、超时设置不合理、SELinux 拦截、反向代理配置写错。如果你的 Nginx 直接返回 404 或 403,那是另一类问题,不在本文范围。
环境与前置条件
- 操作系统:主流 Linux 发行版(CentOS / RHEL / Rocky / AlmaLinux / Ubuntu / Debian 均可),本文命令兼顾 systemd 与 sysvinit
- Nginx:版本不限,配置语法以官方文档当前版本为准
- 权限:需要 root 或 sudo,因为要看进程、端口、日志、SELinux 状态
- 上游服务:任意 HTTP 服务,本文示例用
127.0.0.1:3000 - 磁盘:
/var/log至少留出几百 MB,日志写满也可能导致异常 - 无需额外依赖,
curl、ss、journalctl、tail都是系统自带或常见工具
分步骤部署
第 1 步:确认错误来自哪一层
先分清是"全部请求 502"还是"部分请求 502",两者排查方向完全不同。
```bash
看错误日志,实时跟踪
tail -f /var/log/nginx/error.log
```
同时用另一个终端反复访问:
```bash
curl -I http://127.0.0.1/
```
典型日志长这样:
```
connect() failed (111: Connection refused) while connecting to upstream
```
或
```
upstream timed out (110: Connection timed out) while reading response header
```
Connection refused 指向上游没在监听;timed out 指向上游太慢或超时配置太小。日志里会带 upstream: "http://127.0.0.1:3000/..." 字样,直接告诉你 Nginx 在连哪个地址——先记下这个地址,后面每一步都用它。
第 2 步:确认上游服务是否存活
```bash
假设上游是 3000 端口
ss -lntp | grep 3000
```
成功输出应类似:
```
LISTEN 0 511 127.0.0.1:3000 0.0.0.0:* users:(("node",pid=1234,fd=20))
```
如果什么都没有,说明上游根本没监听。这时去查上游服务的状态:
```bash
systemctl status myapp
journalctl -u myapp -n 100 --no-pager
```
如果上游容器化部署:
```bash
docker ps -a | grep myapp
docker logs --tail 100 myapp
```
注意一个容易踩的点:上游监听的是 127.0.0.1:3000,而 Nginx 配置里写的是 localhost:3000。当 /etc/hosts 或解析顺序把 localhost 指向 ::1(IPv6)时,就会连接失败。改成 127.0.0.1:3000 可以排除这个变量。
第 3 步:绕过 Nginx 直接压上游
这一步是分水岭:直接打上游能通,问题就在 Nginx;打不通,问题在上游。
```bash
curl -v http://127.0.0.1:3000/
```
- 返回 200/正常内容 → 上游健康,继续第 4 步
- 返回
Connection refused→ 上游没监听 - 长时间卡住 → 上游处理逻辑阻塞(比如同步查询慢、连接池耗尽)
- 返回 500 → 上游自己的 bug,先修上游
顺手测一下延迟:
```bash
curl -o /dev/null -s -w "connect=%{time_connect} total=%{time_total}\n" http://127.0.0.1:3000/
```
如果 total 经常超过几秒,就该调到第 5 步去改超时。
第 4 步:检查反向代理配置本身
打开站点配置,常见位置:
```bash
ls /etc/nginx/conf.d/
ls /etc/nginx/sites-enabled/ # Debian / Ubuntu
nginx -T | grep -A 20 "proxy_pass"
```
nginx -T 会把所有 include 的配置合并打印出来,比逐个文件翻更快。重点核对四件事:
```nginx
location / {
proxy_pass http://127.0.0.1:3000;
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_http_version 1.1;
proxy_set_header Connection "";
}
```
proxy_pass的协议、地址、端口是否和上游实际监听一致- 末尾有没有多余的斜杠。
proxy_pass http://127.0.0.1:3000/;和proxy_pass http://127.0.0.1:3000;在路径拼接上行为不同,路径错位可能让上游返回 404 而不是 502,但也常被误判 - 如果用了
upstream块,检查server列表里的地址和权重 - WebSocket 场景必须显式升级协议,否则连接会被上游拒绝:
```nginx
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
```
改完先测语法,再平滑重载:
```bash
nginx -t
systemctl reload nginx
```
nginx -t 输出 syntax is ok 和 test is successful 才算通过。
第 5 步:调超时参数
默认 proxy_read_timeout 是 60 秒。上游接口偶尔跑十几秒的,可以适当放宽:
```nginx
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 120s;
send_timeout 120s;
}
```
含义分别是:
proxy_connect_timeout:与上游建立 TCP 连接的等待时间proxy_send_timeout:把请求体发给上游的等待时间proxy_read_timeout:两次读操作之间的间隔上限(不是整个请求的总时长,这一点常被误解)send_timeout:向客户端发送响应的等待时间
如果日志里是 upstream timed out,先确认上游真的有慢查询,再调大数值。单纯把超时设成很大只是把问题藏起来,客户端的等待时间会一起变长。同时检查上游侧的连接池、线程数、数据库慢查询,这些才是根因。
第 6 步:检查 SELinux
CentOS / RHEL / Rocky 系默认开启 SELinux,它会阻止 Nginx 向非标准端口或非标准路径发起连接。Ubuntu / Debian 默认不开,可跳过。
```bash
getenforce
```
返回 Enforcing 就说明处于强制模式。查最近的拒绝记录:
```bash
ausearch -m avc -ts recent
或
dmesg | grep -i avc
```
如果看到 denied { name_connect } 且进程是 nginx,就是 SELinux 拦了。查看 Nginx 允许连接的网络端口:
```bash
semanage port -l | grep http_port_t
```
常见允许的是 80、443、8000、8080 等。如果你的上游跑在 3000、5000、9000,多半不在列表里。两种处理方式:
```bash
方式一:把你的端口加入允许列表(推荐)
semanage port -a -t http_port_t -p tcp 3000
方式二:临时排查用,设为宽容模式确认是不是 SELinux 导致
setenforce 0
```
setenforce 0 只是验证手段,确认原因后应改回 setenforce 1,用方式一或自定义策略解决。如果 semanage 命令不存在:
```bash
RHEL / CentOS / Rocky
dnf install policycoreutils-python-utils
Ubuntu / Debian
apt install policycoreutils-python-utils
```
另外,如果上游是通过 Nginx 之外的路径访问文件(比如 PHP-FPM 的 socket、静态目录),目录的 SELinux 上下文也要对,用 restorecon -Rv /path/to/dir 修复。
第 7 步:确认没有隐式的上游限制
几个不常被注意但会引发 502 的点:
```bash
文件描述符上限,上游和 nginx 都要看
ulimit -n
cat /proc/$(cat /run/nginx.pid)/limits | grep "open files"
```
还有 worker_connections、worker_rlimit_nofile、上游服务自己的最大连接数。高并发下连接被拒,日志里会出现 worker_connections are not enough 或 Too many open files。
验证部署是否成功
按顺序执行,全部通过说明链路正常:
```bash
1. Nginx 配置语法
nginx -t
2. Nginx 进程在跑
systemctl is-active nginx
3. 上游在监听
ss -lntp | grep 3000
4. 直接打上游
curl -o /dev/null -s -w "%{http_code}\n" http://127.0.0.1:3000/
5. 经过 Nginx
curl -o /dev/null -s -w "%{http_code}\n" http://127.0.0.1/
```
预期结果:第 1 步输出 test is successful;第 2 步输出 active;第 3 步能看到监听行;第 4、5 步都输出 200。然后持续观察错误日志:
```bash
tail -f /var/log/nginx/error.log
```
正常访问时不应再有新的报错行写入。如果第 4 步 200 而第 5 步 502,问题一定在第 4 到第 5 步之间,回到第 4、5、6 步。
常见报错与解决
报错一:connect() failed (111: Connection refused) while connecting to upstream
原因:上游没在指定地址端口监听,或监听在 IPv6 而配置写的是 IPv4(反之亦然),或上游刚崩溃。
解决:
```bash
ss -lntp | grep 3000
systemctl restart myapp
若监听在 ::1,把 proxy_pass 改成 127.0.0.1
```
报错二:upstream timed out (110: Connection timed out) while reading response header
原因:上游处理超过 proxy_read_timeout,常见于慢 SQL、外部 API 调用、冷启动。
解决:
```bash
先定位上游慢在哪
curl -o /dev/null -s -w "total=%{time_total}\n" http://127.0.0.1:3000/slow
再在 nginx 配置中适度调大
proxy_read_timeout 120s; 然后 nginx -t && systemctl reload nginx
```
报错三:connect() to 127.0.0.1:3000 failed (13: Permission denied) while connecting to upstream
原因:SELinux 拒绝 Nginx 连接该端口。这个报错和 Connection refused 很像,注意区分关键字 Permission denied。
解决:
```bash
ausearch -m avc -ts recent | grep nginx
semanage port -a -t http_port_t -p tcp 3000
systemctl restart nginx
```
报错四:upstream sent invalid header while reading response header
原因:上游返回的响应头不符合 HTTP 规范,比如含非法字符、换行错误,或者上游其实是 HTTPS 而配置里写了 http://。
解决:确认协议一致,检查上游框架是否输出了非法头部。
```bash
curl -v http://127.0.0.1:3000/ 2>&1 | head -30
```
报错五:no live upstreams while connecting to upstream
原因:使用了 upstream 块并配置了健康检查,所有后端被标记为不可用。
解决:检查各后端是否真在跑,适当调整 max_fails 与 fail_timeout,避免瞬时抖动把全部节点摘掉。
```nginx
upstream backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
}
```
后续维护
日志轮转:确认 logrotate 对 Nginx 日志生效,避免磁盘写满导致服务异常。
```bash
cat /etc/logrotate.d/nginx
logrotate -d /etc/logrotate.d/nginx # 干跑测试
```
备份配置:改配置前先复制一份带日期的副本。
```bash
cp -a /etc/nginx /etc/nginx.bak.$(date +%F)
```
升级:升级 Nginx 或上游服务前,先在测试环境跑一遍 nginx -t 和一轮回归访问。版本与兼容性以官方文档当前版本为准,不要跨大版本直接上生产。
监控:至少监控三项——Nginx 的 5xx 比例、上游服务存活状态、upstream_response_time。日志格式里加上耗时字段更容易定位:
```nginx
log_format timed '$remote_addr "$request" $status '
'upstream=$upstream_addr rt=$upstream_response_time';
access_log /var/log/nginx/access.log timed;
```
定期自查:把本文第 2 至第 6 步的命令做成一个巡检脚本,每天跑一次,比出故障后再翻日志省事得多。502 的排查核心思路始终是先分层、再定位、后修复,不要在没确认上游状态的情况下反复重启 Nginx。
