Nginx 反向代理的 12 个高频坑:从 502 到 WebSocket 断连

举报
yd_290186593 发表于 2026/11/02 14:05:08 2026/11/02
【摘要】 502/504 九成是上游问题不是 nginx。12 个高频坑逐个给症状、根因、最小修复:容器 localhost、WebSocket 缺 Upgrade 头、root 与 alias 拼错、gzip 不生效。

Nginx 配置看起来简单,出问题时却很难定位:日志里只有一行 `502 Bad Gateway`,或者页面能打开但 WebSocket 一直重连。这篇把实际排障中反复遇到的 12 个问题整理成对照表,每个都给症状、根因和最小修复。

1. 502 Bad Gateway:上游根本没起来

**症状**:`error.log` 里 `connect() failed (111: Connection refused) while connecting to upstream`

**根因**:`proxy_pass` 指向的端口没有服务监听。

**排查**:

# 确认上游端口是否真的在听
ss -ltnp | grep :8080

# 直接绕过 nginx 测试上游
curl -sv http://127.0.0.1:8080/health

**修复**:上游服务没起,或者 `proxy_pass` 写错端口。注意 `proxy_pass http://localhost:8080` 里 `localhost` 可能解析到 IPv6 的 `::1`,而上游只监听了 IPv4。改成 `127.0.0.1` 更稳妥。

2. 502:上游是 Docker 容器但用 localhost

**症状**:宿主机 `curl 127.0.0.1:8080` 通,但 nginx 报 502。

**根因**:nginx 跑在容器里,容器内的 `localhost` 是自己,不是宿主机。

**修复**:

# 错误:容器内 localhost 指向自己
proxy_pass http://localhost:8080;

# 正确:用宿主机内网 IP 或 Docker 服务名
proxy_pass http://172.17.0.1:8080;
# 或(同一 compose 网络)
proxy_pass http://app:8080;

宿主机 IP 用 `ip route | grep default` 查网关地址,通常是 `172.17.0.1` 或 `172.18.0.1`。

3. 504 Gateway Timeout:默认超时太短

**症状**:接口跑 90 秒就断,日志 `upstream timed out (110: Connection timed out)`

**根因**:Nginx 默认 `proxy_read_timeout` 是 60 秒。

**修复**:

location /api/long-task/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_connect_timeout 10s;
    proxy_send_timeout    300s;
    proxy_read_timeout    300s;
}

**注意**:超时是三层叠加的——客户端、nginx、上游。只调 nginx 不够,上游框架(如 Gunicorn 的 `--timeout`)也要放宽。

4. 413 Request Entity Too Large:上传文件被拒

**症状**:上传超过 1MB 的文件报 413。

**根因**:`client_max_body_size` 默认 1m。

**修复**:

# 全局
client_max_body_size 100m;

# 或只对上传接口放宽
location /api/upload {
    client_max_body_size 100m;
    proxy_pass http://127.0.0.1:8080;
}

**坑**:如果前面还有一层 CDN 或负载均衡,它们的限制也要同步调,否则 nginx 放宽了还是被前面拦。

5. WebSocket 连不上:缺 Upgrade 头

**症状**:WebSocket 反复重连,浏览器控制台 `WebSocket connection failed`。

**根因**:Nginx 默认按 HTTP/1.0 转发,不传递 `Upgrade` 和 `Connection` 头。

**修复**:

location /ws {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 3600s;
}

**三个必填项**:`proxy_http_version 1.1`(必须,1.0 不支持 Upgrade)、`Upgrade` 头、`Connection: upgrade`。

**坑**:`proxy_read_timeout` 默认 60 秒,WebSocket 空闲超过 60 秒会被断开。长连接场景要调到 3600s 或更大。

6. WebSocket 每隔 60 秒断一次

**症状**:连接建立成功,但每分钟断一次,客户端自动重连。

**根因**:就是上面说的 `proxy_read_timeout` 默认 60 秒。

**修复**:

proxy_read_timeout 3600s;
proxy_send_timeout 3600s;

**另一个思路**:让客户端定时发心跳(每 30 秒 ping 一次),保持连接活跃。但服务端超时还是要放宽,否则服务端空闲仍会断。

7. 静态资源 404:root 和 alias 用错

**症状**:`/static/app.js` 报 404,但文件确实在 `/var/www/static/app.js`。

**根因**:`root` 和 `alias` 的路径拼接规则不同。

# root:拼接 location + URI
location /static/ {
    root /var/www;          # 实际查找 /var/www/static/app.js ✅
}

# alias:替换 location 部分
location /static/ {
    alias /var/www/static/; # 实际查找 /var/www/static/app.js ✅
}

# 错误:alias 末尾少斜杠
location /static/ {
    alias /var/www/static;  # 实际查找 /var/www/staticapp.js ❌
}

**记忆口诀**:`root` 保留 location 前缀,`alias` 替换掉。`alias` 的路径末尾斜杠要和 location 一致。

8. 子路径部署:前端路由全 404

**症状**:应用部署在 `/app/` 下,首页能打开,但刷新 `/app/user/1` 就 404。

**根因**:SPA 使用 history 路由,nginx 找不到对应文件。

**修复**:

location /app/ {
    alias /var/www/app/;
    index index.html;
    try_files $uri $uri/ /app/index.html;
}

**坑**:`try_files` 的 fallback 路径是**相对于 root 的 URI**,不是文件系统路径。配合 `alias` 时容易写错,建议改用 `root` 或显式 rewrite。

9. 真实 IP 全是 127.0.0.1

**症状**:应用日志里客户端 IP 都是 `127.0.0.1` 或负载均衡的 IP。

**根因**:Nginx 转发时没传原始 IP。

**修复**:

location / {
    proxy_pass http://127.0.0.1:8080;
    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;
}

**应用侧也要配合**:大多数框架默认不信任 `X-Forwarded-For`,需要显式配置信任的代理 IP。例如 Flask 用 `ProxyFix`,Express 设 `app.set('trust proxy', 1)`。

10. HTTPS 混合内容被拦

**症状**:页面是 HTTPS,但部分资源加载失败,控制台 `Mixed Content`。

**根因**:应用生成的链接用了 `http://`,因为 nginx 传给上游的 `$scheme` 是 `http`(nginx 与上游之间是明文)。

**修复**:

proxy_set_header X-Forwarded-Proto $scheme;

应用侧读取这个头来生成绝对 URL。

11. gzip 开了但没生效

**症状**:响应头没有 `Content-Encoding: gzip`。

**根因**:三种常见原因。

gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
gzip_vary on;

排查顺序:

1. **`gzip_types` 没包含该 MIME 类型** —— 默认只压 `text/html`

2. **响应小于 `gzip_min_length`** —— 小文件压缩反而变大

3. **上游已经压缩过** —— 双重压缩会被跳过,检查上游是否也开了 gzip

4. **经过 CDN** —— CDN 可能已经处理,源站不用再压

12. 修改配置后不生效

**症状**:改了 `nginx.conf`,`nginx -s reload` 也执行了,但行为没变。

**排查清单**:

# 1. 语法检查(必须先做)
nginx -t

# 2. 确认改的是正在用的配置文件
nginx -T | grep -A5 "server_name your.domain"

# 3. 确认 reload 成功(不是 restart)
ps aux | grep nginx

# 4. 检查是否有 include 覆盖
grep -r "include" /etc/nginx/nginx.conf

**最常见的坑**:`sites-enabled/` 下的软链接指向了旧的 `sites-available/` 文件,或者有多个 `server` 块匹配同一个域名,先匹配的生效。

**`nginx -T`** 会打印合并后的完整配置,这是排查配置问题的第一命令。

排障顺序总结

遇到问题按这个顺序查,能覆盖 80% 的情况:

| 步骤 | 命令 | 目的 |

|---|---|---|

| 1 | `nginx -t` | 配置语法 |

| 2 | `nginx -T` | 实际生效的配置 |

| 3 | `tail -f /var/log/nginx/error.log` | 错误原因 |

| 4 | `curl -sv http://127.0.0.1:PORT/` | 上游是否通 |

| 5 | `ss -ltnp \| grep :PORT` | 端口是否监听 |

**关键认知**:502/504 这类错误,**九成是上游问题,不是 nginx 问题**。先用 `curl` 直连上游确认,再回来查 nginx 配置,能省大量时间。

小结

这 12 个坑可以归成四类:

1. **连通性**(1、2):上游没起、容器网络隔离

2. **超时与大小**(3、4):默认值太小

3. **协议头**(5、6、9、10):Upgrade、X-Forwarded-*

4. **路径与配置**(7、8、11、12):root/alias、try_files、gzip、配置覆盖

真正难的不是记住这些配置,是**建立"先确认上游、再查 nginx"的排查顺序**。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。