VPS 上后端直接访问正常,加了 Nginx 反向代理却变成 404?先对照上游收到的路径。外部请求是 /api/users,后端需要的可能是 /users,也可能本来就是 /api/users。proxy_pass 多写或少写一个斜杠,就可能把请求送到另一个路由。
这篇针对普通 HTTP 前缀 location,讲清保留、去掉和替换路径前缀。不要在尚未确认上游 URL 时同时加 rewrite、重定向和页面回退,否则很难判断哪一层改了请求。
在 VPS 上对照后端真实路由测试。下面端口只是示例,换成实际监听地址;容器里的 127.0.0.1 也不一定是宿主机或另一个容器。
curl -i 'http://127.0.0.1:3000/users?limit=1'
curl -i 'http://127.0.0.1:3000/api/users?limit=1'
curl -i 'https://app.example.com/api/users?limit=1'
sudo nginx -T
若接口需要登录,未带凭据返回 401/403 不能据此判断路由不存在。结合后端访问日志确认实际路径、请求方法和状态码,并检查域名命中的 server 与 location。分享日志和配置前,去掉令牌、Cookie 和内部敏感地址。
若现有访问日志包含 $status 和 $upstream_status,对照客户端状态与上游状态。上游状态为空时,继续检查本地返回、缓存或是否进入代理,不要直接认定是后端路由错误。错误页可能经过自定义处理,单看页面样式也不能确定来源。
后端根本连不上而出现 502,优先检查地址、端口和容器网络,参考 502 / 504 排查。单纯换斜杠通常解决不了连接拒绝。
假设请求为 /api/users?limit=1,没有额外 rewrite,location 是 /api/:
| proxy_pass 的值 | 上游收到的请求路径 | 适合的后端路由 |
|---|---|---|
http://127.0.0.1:3000 | /api/users?limit=1 | 自己就包含 /api/ |
http://127.0.0.1:3000/ | /users?limit=1 | 路由从 /users 开始 |
http://127.0.0.1:3000/v1/ | /v1/users?limit=1 | 需要改成 /v1/ 前缀 |
这里不是“斜杠越多越规范”。关键是地址后有没有 URI 部分:单独的 / 也是 URI。Nginx 的 proxy_pass 定义说明,带 URI 时会用它替换匹配到的规范化前缀;不带 URI 则保留原请求 URI,发生内部改写时另有规则。
location /api/ {
proxy_pass http://127.0.0.1:3000;
}
后端已经配置了 /api/ 路由时,这个例子不应该再加尾部 /。否则 Nginx 会把前缀去掉,后端收到 /users,自然可能找不到对应接口。
location /api/ {
proxy_pass http://127.0.0.1:3000/;
}
这里 /api/ 被替换成 /。不要为了“去前缀”再叠加一条 rewrite;先用这一层映射验证实际结果。如果去掉前缀后接口正常,而文档或登录跳转仍错,要继续检查应用的外部地址设置。
location /api/ {
proxy_pass http://127.0.0.1:3000/v1/;
}
/api/users 会送到 /v1/users。注意两个边界都保留 /;若把目标写成 /v1,拼接剩余路径时可能得到 /v1users,这不是同一个路由。
上面三段是互斥的 location 片段,只选与后端路由一致的一段,合入现有站点。保留原有证书、鉴权、请求头、限流和其他路径,不要把三段同时放进同一 server。WebSocket 与流式接口还需要对应的代理配置,不能只靠路径映射保证工作。
使用以 / 结尾、由代理指令处理的前缀 location 时,不带尾部斜杠的同名路径可能触发 Nginx 的自动 301。例如这类 /api/ 配置下,请求 /api 会被引导到 /api/,见 location 的特殊处理。
排查时先用不带 -L 的 curl 看第一跳,不要让自动跟随重定向隐藏了问题。登录回调和 POST 接口尤其应该确认最终方法与地址,不要依赖客户端恰好按预期处理 301。
如果业务确实需要 /api 本身作为独立接口,可以为它设计精确匹配,并明确对应的上游路由。不要随手把所有请求都 301 到带斜杠版本。普通 /api/ 前缀也不会把 /apiary 当作同一路径处理。
表格中的简单映射会保留示例查询参数。POST 请求还要验证方法、请求体和 Content-Type;“GET 通了”并不能证明订单提交或文件上传正常。若原配置修改了代理方法、请求体或请求头,应同时检查这些指令。
接口路径能改,不代表应用所有 URL 都会跟着改。后端返回的绝对跳转、HTML 中的资源地址、Cookie Path,以及登录回调,可能仍按原来的根路径生成。先配置应用官方支持的外部 URL 或基路径,再判断是否需要代理层响应改写。
如果应用不支持部署在子路径,独立子域名往往更容易维护。不要用全站 HTML 字符串替换,把每个 / 都改成 /api/;这容易把资源、接口和第三方链接一起改错。
正则 location、命名 location、包含变量的 proxy_pass,以及 rewrite ... break 改写后的请求,都可能采用不同规则。看到 $request_uri、捕获组或多层内部跳转时,回到实际配置和官方定义逐项检查。
同一站点有多条 location 时,也要检查是否被其他更具体或正则规则接管。带 URL 编码、重复斜杠或签名参数的请求,应保留原始请求记录逐项比较,不能把本文的普通路径示例直接当作编码与签名兼容性证明。
也别把 API 404 改成返回前端首页:状态变成 200,但正文是 HTML,调用方仍会解析失败。如果问题发生在单页应用刷新或静态文件,应该看 Vue / React 刷新 404 与资源排查。
先备份站点配置,语法检查通过后再重载:
sudo nginx -t && sudo systemctl reload nginx
curl -i 'https://app.example.com/api/users?limit=1'
用实际允许的测试请求确认四项:上游路径、查询参数、请求方法和请求体,再检查登录跳转与页面资源。生产环境不要用会创建订单、扣款或发消息的请求随意试验。
本文用现有 Nginx 1.19.6 与隔离的模拟后端完成 12 项检查,验证了三种前缀映射、查询参数、POST 内容和自动 301;后端只接受 /root-only 时,保留外部前缀返回 404,去掉前缀后返回 200。该本机版本不作为生产安装建议。测试不覆盖真实业务鉴权、TLS、CDN、WebSocket、浏览器登录或响应中的 URL 改写。
