适用场景
-
Nginx 作为反向代理,后端为 Python Web 应用(Django/Flask/FastAPI 等)
-
使用 uWSGI 或 Gunicorn 作为 WSGI 服务器
-
错误日志中出现:
502 Bad Gateway,对应的 upstream 为 Python 后端
一、连接与网络层
1.1 后端服务未启动 / 进程崩溃
现象
Nginx error.log:
text
connect() failed (111: Connection refused) while connecting to upstream
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看后端进程 | `ps aux | grep -E "uwsgi |
| 检查 systemd 状态 | systemctl status your-app.service |
适用于 systemd 管理的服务 |
| 尝试直接访问后端 | curl -v http://127.0.0.1:8080/health |
替换为你的后端监听地址和端口 |
| 查看后端日志 | tail -100 /var/log/uwsgi/app.log journalctl -u your-app -n 50 |
找到应用级日志路径 |
| 手动启动测试 | uwsgi --ini /path/to/uwsgi.ini |
前台运行,观察是否有错误退出 |
1.2 端口被占用 / 监听地址错误
现象
后端日志出现:Address already in use
Nginx 无法连接,error.log 可能没有明确错误(但 curl 会失败)
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看端口占用 | `ss -tlnp | grep 8080` |
| 检查监听地址 | 同上,看是 0.0.0.0:8080 还是 127.0.0.1:8080 |
Nginx 必须能访问该 IP |
| 查看 uWSGI 配置 | `grep -E "socket | http-socket" /path/to/uwsgi.ini` |
| 查看 Gunicorn 配置 | grep bind /path/to/gunicorn.conf.py |
bind = '0.0.0.0:8000' |
| 杀掉占用进程后重启 | fuser -k 8080/tcp |
慎用,会强制结束占用端口的进程 |
1.3 防火墙 / 安全组拦截
现象
从 Nginx 机器上 telnet 后端IP 后端端口 不通
Nginx error.log:connect() failed (113: No route to host) 或 Connection timed out
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 从 Nginx 机器测试连通性 | telnet 10.0.0.2 8080 |
替换为后端实际 IP 和端口 |
| 查看本机防火墙 | iptables -L -n -v firewall-cmd --list-all |
检查是否有 DROP/REJECT 规则 |
| 检查云平台安全组 | 登录控制台,查看入站规则 | 必须允许 Nginx 所在机器的 IP 访问后端端口 |
| 临时关闭防火墙测试 | systemctl stop firewalld(CentOS) ufw disable(Ubuntu) |
仅测试,不要长期关闭 |
| 查看 SELinux 阻止 | ausearch -m avc -ts recent |
如果启用 SELinux,可能会拒绝网络连接 |
1.4 Unix Socket 文件权限错误
现象
Nginx error.log:
connect() to unix:/path/to/uwsgi.sock failed (13: Permission denied)
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看 socket 文件权限 | ls -l /path/to/uwsgi.sock |
例如 srw-rw---- 1 appuser appgroup 0 ... |
| 查看 Nginx 运行用户 | `ps aux | grep nginx` |
| 修改 socket 文件所属组 | chgrp www-data /path/to/uwsgi.sock |
将 Nginx 用户加入 socket 组 |
| 修改目录权限 | chmod 755 /path/to/dir/containing/sock |
保证 Nginx 用户能进入目录 |
| 在 uWSGI 配置中指定用户/组 | uid = www-data gid = www-data chmod-socket = 660 |
启动时自动设置权限 |
1.5 Nginx 与后端网络不通
现象
Nginx error.log:connect() failed (110: Connection timed out)
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 测试基础连通性 | ping 后端IP |
确认网络层可达 |
| 路由跟踪 | traceroute 后端IP |
查看经过的跳数,确定哪里阻断 |
| 检查跨主机部署时端口映射 | docker ps 或 netstat -tlnp 在容器宿主机上 |
确认 Docker bridge 或 host 模式端口正确暴露 |
二、应用服务器层(uWSGI / Gunicorn)
2.1 监听队列已满(listen queue full)
现象
uWSGI 日志出现:
uWSGI listen queue of socket "0.0.0.0:8181" (fd: 3) full !!! (513/512)
Nginx 可能报:upstream timed out (110: Connection timed out) 或 connect() to ... failed (11: Resource temporarily unavailable)
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看 uWSGI 日志 | grep "listen queue" /var/log/uwsgi/*.log |
确认队列溢出频率 |
| 查看当前队列大小 | curl http://127.0.0.1:1717/(stats 服务) 或 uwsgi --connect-and-read /tmp/stats.sock |
需提前开启 stats 端口 |
| 查看系统 backlog | `ss -lnt | grep 8181` |
| 查看系统限制 | sysctl net.core.somaxconn |
默认为 128,uWSGI 的实际上限不会超过它 |
| 临时提高系统限制 | sysctl -w net.core.somaxconn=65536 |
立即生效,重启失效 |
| 永久修改 | 编辑 /etc/sysctl.conf,添加 net.core.somaxconn=65536,执行 sysctl -p |
持久化配置 |
| 调整 uWSGI listen | 编辑 uwsgi.ini:listen = 1024 |
重启服务生效 |
| 增加 worker 数量 | processes = 8(或更多) |
提高并发处理能力,减少队列堆积 |
2.2 Worker 全部忙碌(无空闲进程)
现象
请求响应变慢,Nginx 报 upstream timed out (110: Connection timed out),后端 CPU 占用很高
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看 uWSGI stats | curl http://127.0.0.1:1717/ 观察 "workers" 中 "requests" 和 "status":"busy" |
若所有 worker 的 busy 数量等于 processes 数,则表示满负荷 |
| 查看系统负载 | top 或 htop,按 H 显示线程 |
确认 CPU 是否耗尽 |
| 查看每个 worker 状态 | uwsgi --connect-and-read /tmp/stats.sock 返回 JSON |
或安装 uwsgitop 工具 |
| 增加 worker 数量 | processes = 16(uWSGI) workers = 9(Gunicorn) |
一般建议 2*CPU核数+1,需监控内存 |
| 启用线程(混合模式) | threads = 4(每个进程开 4 个线程) |
可提高并发,但注意 GIL |
2.3 Worker 意外崩溃 / 频繁重启
现象
Nginx error.log:upstream prematurely closed connection while reading response header from upstream
uWSGI 日志中出现:DAMN ! worker ... died、respawn 字样
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看 uWSGI 日志上下文 | tail -200 /var/log/uwsgi/app.log |
找到 worker 死亡前的最后几行 |
| 检查应用日志 | tail -100 /var/log/app/app.log |
看是否有未捕获异常导致进程退出 |
| 检查 harakiri 超时 | grep harakiri /etc/uwsgi/*.ini |
若设置 harakiri = 30,超过 30 秒无响应的请求会被强制杀掉 worker |
| 检查 max-requests | max-requests = 1000 |
worker 处理 1000 个请求后重启,有可能在重启瞬间少量 502 |
| 查看系统 OOM | `dmesg -T | grep -i "oom"` |
| 开启 uWSGI 内存报告 | log-master = true log-drain = true memory-report = true |
可在日志中看到每个 worker 的内存占用 |
2.4 uWSGI 缓冲区过小
现象
Nginx error.log:upstream sent too big header while reading response header from upstream
uWSGI 日志:invalid request block size: ...
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看当前 buffer-size | grep buffer-size /path/to/uwsgi.ini |
默认 4096 (4KB) |
| 临时调大测试 | buffer-size = 32768 |
重启 uWSGI 后观察 |
| 检查响应头或 Cookie 大小 | 在应用代码中打印 len(response_headers) |
大 Cookie、JWT 可能导致超限 |
| 同时调整 Nginx 缓冲区 | proxy_buffer_size 16k; proxy_buffers 4 16k; |
在 Nginx location 或 http 块中配置 |
2.5 Gunicorn 超时设置
现象
Gunicorn 日志:[CRITICAL] WORKER TIMEOUT,然后 worker 被 kill,Nginx 收到连接关闭
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看 Gunicorn 配置 | grep timeout /path/to/gunicorn.conf.py |
默认 30 秒 |
| 增加超时时间 | timeout = 120 |
如果应用确实需要长时间处理,可调大 |
| 优化代码减少耗时 | 使用 cProfile 或 py-spy 定位慢函数 |
从根源解决 |
| 查看 Gunicorn 日志 | journalctl -u gunicorn -n 50 |
systemd 下的日志 |
2.6 uWSGI harakiri 超时触发
现象
uWSGI 日志:HARAKIRI: worker 1 (pid:12345) is taking too much time (over 30 seconds),随后 DAMN ! worker 1 died
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看 harakiri 值 | grep harakiri /etc/uwsgi/*.ini |
默认未开启 |
| 临时关闭 harakiri 测试 | 注释掉配置行 | 若问题消失,说明业务确实耗时超过阈值 |
| 调大 harakiri | harakiri = 120 |
给慢请求更多时间 |
| 优化代码 | 使用异步、缓存、消息队列 | 避免同步阻塞 |
三、Python 应用代码层
3.1 未捕获的异常导致 worker 退出
现象
Worker 突然消失,Nginx 502,应用日志有完整 traceback
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看应用日志 | tail -100 /var/log/app/app.log |
寻找 Traceback (most recent call last) |
| 检查全局异常处理 | 查看 app.errorhandler(500) 或 middleware |
确保不会让 worker 崩溃 |
| 添加日志记录所有异常 | 使用 logging.exception(e) 在 except 块中 |
方便定位 |
| 使用 Sentry / 其他错误追踪 | 集成 sentry-sdk |
自动收集未捕获异常 |
3.2 死锁 / 长时间阻塞
现象
所有 worker 逐步卡住,请求超时,CPU 可能不高(线程阻塞等待 I/O)
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看 uWSGI stats | curl 127.0.0.1:1717 查看每个 worker 的 "status" 和 "avg_rt" |
若所有 worker 都处于 busy 状态且平均响应时间很长,说明阻塞 |
| 使用 py-spy 查看调用栈 | pip install py-spy py-spy dump --pid <worker_pid> |
查看卡在哪个函数 |
| 使用 strace 跟踪系统调用 | strace -p <worker_pid> -e trace=network,read,write |
看是否卡在 recv/send |
| 检查数据库连接池 | 查看数据库连接池配置 max_overflow, pool_size |
若连接耗尽会阻塞 |
| 检查外部 API | 用 curl -w "time_total:%{time_total}" 测试外部服务 |
确认没有慢调用 |
| 添加代码超时控制 | 使用 timeout_decorator 或 asyncio.wait_for |
防止无限阻塞 |
3.3 响应 Content-Length 与实际不符
现象
Nginx error.log:upstream sent invalid header: "Content-Length: 100" while reading response header from upstream
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 用 curl 查看响应头 | curl -i http://backend:port/some/url |
观察 Content-Length 与实际 body 长度是否一致 |
| 检查应用代码 | 搜索 Content-Length 头部设置 |
可能手动设置的错误值 |
| 检查中间件 | 某些压缩中间件可能错误修改 Content-Length | 禁用 gzip 测试 |
| 使用 Wireshark 抓包 | tcpdump -i lo -s 0 -A port 8080 |
精确分析响应 |
3.4 返回了过大头部(超长 Cookie / JWT)
现象
Nginx error.log:upstream sent too big header while reading response header from upstream
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看 Nginx 缓冲区当前大小 | grep proxy_buffer /etc/nginx/nginx.conf |
默认 4k/8k |
| 临时增大缓冲区 | proxy_buffer_size 16k; proxy_buffers 8 16k; |
重启 Nginx |
| 检查 Cookie 大小 | `curl -sI http://backend:port | grep -i set-cookie` |
| 优化 Cookie 存储 | 使用服务器端 session,或压缩信息 | 避免存入大量 JSON |
| 检查 JWT token 大小 | 解码后查看 payload 大小 | 避免存入过多自定义字段 |
3.5 应用内部死循环 / 内存泄漏
现象
Worker 内存持续增长,最终被 OOM Killer 杀掉,Nginx 502
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看内存趋势 | top -p <worker_pid> 并持续观察 RES 列 |
若一直上升,疑似泄漏 |
| 使用 memory-profiler | 在代码中插入 @profile 并运行 python -m memory_profiler script.py |
定位泄漏点 |
| 检查全局变量 | 查看是否有不断增长的 list/dict 未清理 | 常见于缓存未设置上限 |
| 启用 uWSGI memory-report | memory-report = true |
日志中每个 worker 的内存使用 |
| 重启 worker 策略 | max-requests = 2000 |
定期回收,缓解泄漏影响 |
四、资源限制层
4.1 文件描述符不足
现象
uWSGI log:accept4() failed: Too many open files
Nginx log:socket() failed (24: Too many open files)
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看当前限制 | ulimit -n |
软限制,默认 1024 较低 |
| 查看进程已打开 fd 数量 | `lsof -p |
wc -l` |
| 临时提高限制 | ulimit -n 65535 |
仅当前 shell 有效 |
| 永久修改(systemd) | 在 service 文件中加 LimitNOFILE=65535 |
然后 systemctl daemon-reload |
| 永久修改(limits.conf) | 编辑 /etc/security/limits.conf,添加: * soft nofile 65535 * hard nofile 65535 |
重启后生效 |
| 检查 socket 泄漏 | `lsof -p |
grep sock` |
4.2 内存不足(OOM Killer)
现象
后端进程突然消失,dmesg -T | grep -i oom 有记录
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看系统内存 | free -h |
观察 available 内存 |
| 查看 OOM 日志 | `dmesg -T | grep -i "killed process"` |
| 检查后端内存设置 | uWSGI 的 limit-as 限制 |
可能设置过小 |
| 增加内存或 swap | dd if=/dev/zero of=/swapfile bs=1M count=2048 && mkswap && swapon |
临时缓解 |
| 优化代码内存占用 | 使用 tracemalloc 或 memory_profiler |
找出内存消耗大的对象 |
| 调整 OOM 分数 | echo -1000 > /proc/<pid>/oom_score_adj |
防止重要进程被杀(不推荐) |
4.3 CPU 限制(容器环境)
现象
应用整体响应慢,队列堆积,超时 502,docker stats 显示 CPU 达到上限
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看容器资源限制 | `docker inspect <container_id> | grep -A5 "CpuQuota"` |
| 查看容器 CPU 使用 | docker stats <container> |
看到 % 接近 limit |
| 增加 CPU 配额 | docker update --cpus=2 <container> |
运行时调整(需支持) |
| 调整 Kubernetes requests/limits | 编辑 deployment yaml:resources: limits: cpu: "2" |
kubectl apply -f |
| 优化应用计算密集部分 | 使用 cProfile 分析性能瓶颈 |
减少不必要的计算 |
4.4 线程数限制
现象
uWSGI 日志:can't create thread,Nginx 502
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看系统最大线程数 | cat /proc/sys/kernel/threads-max |
通常很大 |
| 查看用户进程限制 | ulimit -u |
默认一般 4096 或更多 |
| 检查 uWSGI 线程数 | threads = 2 且 processes = 10,总线程数 = 20 |
应该远低于限制 |
| 提高 ulimit -u | 在 limits.conf 中:* soft nproc 65535 |
重启 |
| 排查线程泄漏 | 使用 `ps -eLf | grep uwsgi |
五、Nginx 与后端协议配置不一致
5.1 HTTP 版本不匹配 + Keep-Alive 冲突
现象
典型的你遇到的场景:后端期望 HTTP/1.1 并复用连接,但 Nginx 默认使用 HTTP/1.0 且关闭 keep-alive,导致后端 reset 连接,Nginx 502
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看 Nginx 配置 | grep -A5 "proxy_pass" /etc/nginx/sites-enabled/default |
确认是否有 proxy_http_version |
| 添加兼容配置 | 在 location 块中加入: proxy_http_version 1.1; proxy_set_header Connection ""; |
使 Nginx 与后端间使用 HTTP/1.1 长连接 |
| 检查后端 keep-alive 设置 | uWSGI:http-keepalive = 1 Gunicorn:keepalive = 5 |
确认后端已启用 |
5.2 Nginx 使用 uwsgi_pass 但后端是 HTTP 协议
现象
直接 502,没有明确错误;后端日志无任何访问记录
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看 Nginx 配置 | grep "uwsgi_pass" /etc/nginx/conf.d/ |
如果用了 uwsgi_pass,后端必须使用 uWSGI 协议(socket 而不是 http-socket) |
| 检查 uWSGI 协议 | 后端 uWSGI 配置中必须是 socket = 0.0.0.0:3031,而不是 http-socket |
uwsgi 协议比 HTTP 更高效 |
| 如果要使用 HTTP 协议 | Nginx 改用 proxy_pass http://backend:port |
同时后端应使用 http-socket 或直接是 Gunicorn HTTP 监听 |
5.3 upstream 中 server 为域名且解析失败
现象
Nginx 启动时 warn 或 reload 后 502,但后端明明在运行
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 检查 upstream 配置 | grep -A2 "upstream" /etc/nginx/conf.d/ |
看 server 用的是 IP 还是域名 |
| 测试域名解析 | nslookup backend.local |
确保解析正常 |
| 使用 IP 代替域名 | 直接写 server 10.0.0.2:8080 |
避免 DNS 解析问题 |
| 配置 resolver | 在 nginx.conf 中加: resolver 8.8.8.8 valid=30s; |
适用于动态 upstream |
5.4 后端的 backlog 与 Nginx 的队列冲突
现象
突发流量时少量 502,平时正常
排查方法
| 步骤 | 命令 / 路径 | 说明 |
|---|---|---|
| 查看系统 backlog 使用 | `ss -lnt | grep :8080` |
| 提高 Nginx 的 proxy_connect_timeout | proxy_connect_timeout 5s; |
给后端更多时间建立连接 |
| 协调 uWSGI listen 与 Nginx 超时 | listen = 1024 proxy_connect_timeout 3s |
确保队列不会过早塞满 |
六、综合排查脚本
将以下脚本保存为 debug_502.sh,遇到 502 时快速收集现场:
bash
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
OUTDIR="/tmp/502_debug_$DATE"
mkdir -p "$OUTDIR"
# 1. Nginx 错误日志
tail -50 /var/log/nginx/error.log > "$OUTDIR/nginx_error.log"
# 2. 后端日志(根据实际修改路径)
tail -50 /var/log/uwsgi/app.log > "$OUTDIR/uwsgi.log" 2>/dev/null
tail -50 /var/log/gunicorn/access.log > "$OUTDIR/gunicorn.log" 2>/dev/null
journalctl -u your-app -n 50 > "$OUTDIR/systemd.log" 2>/dev/null
# 3. 进程状态
ps aux | grep -E "uwsgi|gunicorn|python" > "$OUTDIR/ps.txt"
# 4. 端口监听
ss -tlnp > "$OUTDIR/ss_tlnp.txt"
# 5. 队列状态(假设后端端口 8181)
ss -lnt | grep :8181 > "$OUTDIR/queue_status.txt"
# 6. 系统限制
ulimit -a > "$OUTDIR/ulimit.txt"
sysctl net.core.somaxconn > "$OUTDIR/somaxconn.txt"
cat /proc/sys/fs/file-max > "$OUTDIR/file_max.txt"
# 7. 内存使用
free -h > "$OUTDIR/free_h.txt"
dmesg -T | grep -i "oom" > "$OUTDIR/oom.txt" 2>/dev/null
echo "Debug info collected in $OUTDIR"
tar -czf "$OUTDIR.tar.gz" "$OUTDIR"
echo "Compressed archive: $OUTDIR.tar.gz"
附录:常用配置速查
uWSGI 生产推荐配置片段
ini
[uwsgi]
socket = 0.0.0.0:8181
processes = 8
threads = 2
listen = 1024
buffer-size = 32768
harakiri = 60
max-requests = 2000
memory-report = true
log-master = true
Nginx 反向代理配置片段
nginx
location / {
proxy_pass http://backend_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
}