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