502 Bad Gateway 是 Nginx 在反向代理模式下最常见的问题之一。如果你刚把 Django 项目部署到阿里云 ECS,看到这个错误,先别急着怀疑代码——多半是上游服务没跑起来,或者 Nginx 没找到它。下面是我自己处理这类问题时走的排查路径,你可以参考。
先确认 Nginx 是否感知到上游
Nginx 返回 502,说明它把请求转发给了后端(比如 uwsgi 或 gunicorn),但后端没有正常响应。所以第一步不是改代码,而是确认 Nginx 配置里的 upstream 地址和端口对不对得上。打开你的 Nginx 配置文件,通常放在 /etc/nginx/sites-enabled/ 或 /etc/nginx/conf.d/ 下,找到 proxy_pass 或者 uwsgi_pass 那一行。
location / {
uwsgi_pass 127.0.0.1:8001;
include uwsgi_params;
}关键看两点:第一,端口号(比如 8001)是否和你启动的 Django 进程监听端口一致;第二,如果用了 socket 文件,路径是否正确。一个常见的疏忽是,Django 进程绑定了 0.0.0.0:8000,而 Nginx 配置里却写 127.0.0.1:8001,端口不匹配就会 502。此时直接查看 Nginx 的 error.log(通常位于 /var/log/nginx/error.log),如果看到类似 connect() failed (111: Connection refused) 的记录,就能确认是上游没在这端口上听。这时要修正端口,或者重启 Django 进程到正确的端口。
如果 error.log 里没有明显拒绝连接,而是 upstream prematurely closed connection,那说明后端进程曾经连上过但中途退出了,需要去查 Django 的启动日志。
检查 Django 进程是否存活
用 ps aux | grep uwsgi 或者 ps aux | grep gunicorn 看看进程是否存在。如果没有,说明启动脚本没生效,或者进程崩溃了。启动 Django 项目时,我习惯先前台运行一次,确认没有报错:
# 用 uwsgi 的示例
uwsgi --ini /path/to/uwsgi.ini # 看终端输出,不要加 -d 暂时后台
# 或者 gunicorn
cd /path/to/project && gunicorn -w 4 -b 0.0.0.0:8001 myproject.wsgi:application如果能正常运行且不报错,再按 Ctrl+C 停止,改用 supervisor 或 systemd 后台启动。要是前台就有错,比如 ModuleNotFoundError 或数据库连接失败,那就先修复这些。常见的 Django 启动失败原因有:虚拟环境路径不对、pip 包没装全、数据库配置里用了内网域名但在 ECS 上不可达。建议启动时加上 --log-level debug 选项,把日志输出到文件里观察。
另外别忘了检查 Django 项目的静态文件和媒体文件路径。如果 Nginx 配置里单独用 location /static/ 直接处理静态文件(不走 Django),而该目录不存在或权限不对,也可能间接导致 502——因为 Nginx 在返回静态文件之前先尝试了该 location,但失败后可能退回到代理,不过更多情况下是直接 404 而不是 502,所以优先级不高。
阿里云安全组和防火墙别漏掉
很多新手会在 ECS 内部启动 Django 进程并绑定 0.0.0.0:8000,但 Nginx 也在本机,所以端口通常没问题。可如果 Nginx 部署在不同机器(比如负载均衡器),或者你想从外网直接调试后端端口,就要检查安全组规则。阿里云控制台里找到你的 ECS 实例,查看“安全组”入方向,是否放行了对应的端口(比如 8000)。一般生产环境不对外开放后端端口,但调试时可以临时放行自己的 IP。
另一个容易被忽略的是 ECS 内部的防火墙(iptables 或 firewalld)。默认阿里云自带的系统镜像可能没开启,但如果自己装过,记得检查 iptables -L -n 或 systemctl status firewalld。要是有规则阻拦了本地回环(loopback)流量,虽然罕见,但也会导致 Nginx 连不上 127.0.0.1。
查看 uwsgi 或 gunicorn 的具体日志
进程活着不代表没出错。比如 uwsgi 启动后,如果 Django 应用在第一个请求时才载入,而载入过程中发生异常(比如数据库连接超时),uwsgi 可能会直接断开连接,Nginx 就会收到 502。uwsgi 的日志默认输出到 stderr,如果你用 supervisor 管理,日志通常在 /var/log/supervisor/ 下;如果用 systemd,用 journalctl -u your-service 查看。gunicorn 的日志可以用 --access-logfile 和 --error-logfile 指定。
我曾经遇到一个问题:Django 的数据库配置里用了 RDS 的内网地址,但 ECS 和 RDS 不在同一个 VPC 里(经典网络模式下),导致连接被拒。错误日志里会明确写 could not connect to server: Connection timed out。这时候改配置为外网地址(并配合白名单)即可。所以一定要看日志,不要瞎猜。
临时止血:返回静态页面或切换简单应用
如果线上很紧急,可以先让 Nginx 返回一个简单的维护页面,把请求全部挡住,避免用户反复刷到 502。方法是在 Nginx 里加一行:
error_page 502 /502.html;
location = /502.html {
root /path/to/static/html;
}但这只是治标。我个人认为,与其花时间改这个,不如全力排查后端问题。如果实在查不出,可以尝试换一个 WSGI 服务器,比如临时从 uwsgi 换成 gunicorn,或者反向操作——有时候某个版本或配置的 bug 会导致特定的连接问题。换完后重新启动并测试。注意回滚时记得把配置也改回来。
几个自问自答
问:为什么 Nginx 的 error.log 里什么都不写?
答:可能是日志级别设置太高,或者 Nginx 本身没捕捉到错误(比如请求没有到达 Nginx,被阿里云 SLB 直接返回 502)。检查一下是否用了阿里云负载均衡(SLB),如果 SLB 的健康检查失败,它会直接返回 502 而不经过后端。你需要去 SLB 控制台看后端服务器状态。
问:重启所有服务后问题还在,怎么办?
答:先确认你修改的配置确实生效了。Nginx 要 sudo systemctl reload nginx(不是 restart,reload 不中断连接)。Django 进程也要重新加载。如果还是不行,可以写一个最小的 Flask 应用监听同一端口,看 Nginx 能否正常代理。如果能通,说明问题在 Django 项目的依赖或配置上;如果也不能,那就是 Nginx 或者网络层的问题。这个方法能快速缩小范围。