朕喜技术(ZhenxiJS)
2026.09.07
1 分钟阅读

Nginx 反向代理 404:proxy_pass 不带尾斜杠把 /api 前缀转发给后端

2026-09-07 | Nginx / 反向代理 / proxy_pass / location / /api / 404 / 尾斜杠 / reverse proxy / URI 改写

一、问题背景

架构:前端 SPA → Nginx (80) → Node.js API (3000)。后端路由不带 /api 前缀(如 /hello、/users、/health),前端统一通过 fetch('/api/hello') 经 Nginx 反代调用。

配置了 location /api/ { proxy_pass http://app:3000; },结果所有经 /api/* 反代的请求都返回 404,但绕过 Nginx 直连后端 /hello 却返回 200。

二、复现步骤

cd scenario/2026-09-07-0800/
docker compose up -d --build

# 后端直连 → 200 正常
curl http://localhost:3001/hello

# 经 Nginx → 404(复现 bug)
curl http://localhost:8080/api/hello

典型报错/现象:

{"error":"Not Found","path":"/api/hello","hint":"服务端路由列表: / , /hello , /users , /health"}
[HTTP 404]

关键排查命令:

# 看后端实际收到的请求路径(这是定位根因的铁证)
docker compose logs app
# → GET /api/hello   ← Nginx 把 /api 前缀原样转发给了后端

三、根本原因

nginx/default.conf 中 location /api/ 块内:

proxy_pass http://app:3000;    # ❌ 无尾斜杠

Nginx proxy_pass URI 替换规则(location 前缀匹配时):

proxy_pass 写法对 /api/hello后端收到结果
proxy_pass http://app:3000;原样保留/api/hello❌ 404
proxy_pass http://app:3000/;/api/→//hello✅ 200
proxy_pass http://app:3000/xxx/;/api/→/xxx//xxx/hello由后端路由决定

关键区分:http://host:port(不带 URI)与 http://host:port/(带尾斜杠)在 location 匹配场景下行为完全不同。不带 URI 时 Nginx 保留完整原始请求 URI;带 URI(哪怕只是 /)时 Nginx 会用该 URI 替换掉 location 匹配到的前缀。

四、解决办法

把 proxy_pass 加上尾斜杠:

location /api/ {
    proxy_pass http://app:3000/;    # ✅ 关键改动:加尾斜杠
    proxy_set_header Host $host;
    ...
}
docker compose restart nginx

五、验证

curl http://localhost:8080/api/hello       # 200 { "message":"Hello from backend API!" }
curl http://localhost:8080/api/users       # 200 { "users":[...] }
curl http://localhost:8080/api/users/alice # 200 { "user":"alice",... }
curl http://localhost:8080/api/health      # 200 { "status":"ok" }
curl http://localhost:8080/api/            # 200 服务信息

后端日志确认前缀已被剥离:GET /hello、GET /users、GET /health……全部 200。

六、经验与避坑

  1. 直连通 ≠ 反代通:后端能直接访问、经 Nginx 却 404,问题十有八九在 Nginx 的 URI 改写/转发路径,先别看后端业务代码。
  2. 第一时间查后端日志:后端打印的 req.url 是判断「Nginx 到底转发过来什么路径」的最直接证据 —— 看到不该带的 /api 前缀,就是 proxy_pass 没做前缀剥离。
  3. proxy_pass 尾斜杠是重灾区:API 网关(Nginx 挂 /api、后端不感知前缀)是最典型场景,务必用带 / 的 proxy_pass http://backend:port/;。
  4. 不依赖 rewrite 也能改路径:多数人下意识用 rewrite ^/api/(.*) /$1 break;,其实 proxy_pass 带 URI 本身就是最简洁的路径改写手段。
  5. 修复后全量验证:不要只测一个接口,静态路由、带路径参数路由、根路径、健康检查都要跑一遍,避免只修好一种情况。
  6. 对照规则速记:proxy_pass http://host:port;(无 URI)保留 location 前缀;proxy_pass http://host:port/;(带斜杠)用 / 替换 location 前缀。

七、可复现命令(供他人回溯)

cd scenario/2026-09-07-0800/

# 复现 404
docker compose up -d --build
curl http://localhost:8080/api/hello    # → 404

# 修复
sed -i 's|proxy_pass http://app:3000;|proxy_pass http://app:3000/;|' nginx/default.conf
docker compose restart nginx

# 验证 200
curl http://localhost:8080/api/hello    # → 200
docker compose down

版权声明

本文为博主原创文章,遵循 CC BY-NC-SA 4.0 许可协议。 转载请注明出处:http://localhost/blogs/2026-09-07-0807-nginx-proxy-pass

分享这篇文章