DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Nginx 代理 /api/ 返回 404,直接访问后端接口正常

后端 /users 正常,经过 Nginx 的 /api/users 却返回 404,可通过实际请求路径判断是否多保留了 /api/ 前缀。说明 proxy_pass 有无 URI 的区别、末尾斜杠的替换规则,以及 /api 入口的跳转和加载验收。

直接请求后端的 /users 能得到正常响应,换成经过 Nginx 的 /api/users 却返回 404。首页仍能打开,nginx -t 也没有报错。遇到这组现象,要核对后端实际收到的是 /users,还是带着入口前缀的 /api/users。

例如,下面这段配置不会去掉请求中的 /api/:

location /api/ {
    proxy_pass http://127.0.0.1:9000;
}

后端如果只注册了 /users,接到 /api/users 就可能找不到路由。先确定后端需要的路径,再决定是否修改代理地址末尾的斜杠。并非所有代理 404 都能靠加 / 解决。

本文讨论普通前缀 location、固定上游地址,以及没有其他 rewrite 改写请求的场景。示例中的 127.0.0.1:9000 是 Nginx 所在网络环境内的后端地址,请替换成实际可达的地址;如果 Nginx 在容器里,它不代表宿主机。

用同一个接口比较入口路径与后端路径

选一个允许重复读取、不会产生业务写入的接口,在本地或测试环境比较下面三条请求。/users 只是示例,要换成应用已有的只读接口;8080 是示例 Nginx 监听端口,api.example.test 是示例站点名。

curl -i 'http://127.0.0.1:9000/users'
curl -i 'http://127.0.0.1:9000/api/users'
curl -i -H 'Host: api.example.test' 'http://127.0.0.1:8080/api/users'

如果第一条正常、第二条 404、第三条也 404,再结合后端访问日志看到第三条到达的是 /api/users,就有了路径前缀不匹配的直接依据。只看到两个相同的状态码,还不能确定根因。

比较时还要保持请求方法、认证条件和后端所需的 Host 一致。这里假设后端不按 Host 分配不同应用;如果后端存在虚拟主机,应按代理实际发送的 Host 补齐直连请求。Nginx 默认会使用 $proxy_host 作为上游 Host,不能把“IP 直连成功”和“另一个 Host 下的 404”直接归因于路径。

不要只凭响应里的 Server: nginx 判断是谁生成了 404。结合后端请求日志,以及已有代理日志中的 $upstream_status 判断是否收到了上游 404;还要考虑配置中是否启用了错误拦截或自定义错误页。若请求根本没有到达预期后端,先核对选中的站点和 location,此时改斜杠未必有效。

后端不需要 /api/ 时,用 URI 替换前缀

确认外部入口是 /api/users、后端路由是 /users 后,将已有 server 内对应配置改为:

location /api/ {
    proxy_pass http://127.0.0.1:9000/;
}

proxy_pass 中端口后的 / 是一个 URI。Nginx 会把规范化请求路径中匹配 location 的部分替换成这个 URI:/api/ 被 / 替换,后面的 users 保留,后端收到 /users。

如果后端使用另一个固定前缀,例如实际路由为 /internal/users,对应写法是:

location /api/ {
    proxy_pass http://127.0.0.1:9000/internal/;
}

这两段是不同接口约定下的替代方案,不要同时添加两个相同的 location /api/。原有认证、请求头、限流等设置仍需保留,示例不能替代整个站点配置。

也不要把第二段写成 http://127.0.0.1:9000/internal 后期待 Nginx 自动补分隔符。按照前缀替换规则,匹配 /api/users 后剩下 users,接到 /internal 后会成为 /internalusers。当末尾斜杠承担目录分隔作用时,它就是路径的一部分。

后端本来就有 /api/ 时,保留完整路径

如果直连后端时正常的地址就是 /api/users,原来的无 URI 写法通常正符合需求:

location /api/ {
    proxy_pass http://127.0.0.1:9000;
}

在没有额外改写的前提下,它将原请求 URI 传给上游。此时加上末尾 / 反而会把后端需要的 /api/ 删掉。要继续检查 Host、选中的配置块、认证和后端路由,而不是反复切换斜杠碰运气。

上述规则区分的是“有无 URI 部分”。http://127.0.0.1:9000/internal 虽然不以斜杠结尾,仍然带有 URI /internal,并不属于保留原请求 URI 的写法。

正则 location、命名 location、带变量的 proxy_pass,以及同一块内的 rewrite ... break 有各自的处理限制,不能直接套用这里的前缀替换示例。Nginx 1.1.12 之前,无 URI 的代理在部分改写场景下还可能发送原始 URI;不要用旧版本的改写结果推断当前配置。

单独验证 /api 与 /api/ 的入口行为

location /api 是字符串前缀匹配,可能连 /apix 也匹配进去;location /api/ 则明确要求这个斜杠边界。如果 API 都位于该目录下,使用后者更容易表达路由范围。

还需要区分一种入口行为:以 / 结尾的代理前缀 location,对缺少末尾斜杠的同名入口通常会自动返回 301。因此,配置 /api/ 后,应分别检查 /api/users、/api/ 和 /api,不要把最后一个入口的跳转误认为后端路径替换。

如果接口约定要求 /api 本身直接映射到后端 /,可以在保留 /api/ 配置的同时,另外设置精确匹配:

location = /api {
    proxy_pass http://127.0.0.1:9000/;
}

只有后端根路径确实承担这一接口时才使用它。精确匹配块也需要相同的认证等必要设置,不能因新增入口绕过原来的限制。若应用要求统一跳转到 /api/,则按原有接口约定保留跳转,并验证调用端对跳转和请求方法的处理。

加载后以真实路由结果验收

先备份将要修改的配置文件,把副本放到不会被 include 加载的位置。对与目标部署一致的本地配置检查语法:

nginx -t -c /etc/nginx/nginx.conf

主配置路径应替换为实例实际路径。需要查看磁盘上包括 include 在内的配置内容时,可使用 Nginx 1.9.2 起提供的命令:

nginx -T -c /etc/nginx/nginx.conf

检查对应站点内是否有更具体的前缀、精确匹配或正则规则影响请求去向。输出可能包含内部地址和敏感配置,不宜直接公开。-T 展示的是当前读取的配置文件,不能证明运行中的 worker 已加载这次修改。

本地检查通过后,通过既有部署方式更新配置。对于直接管理的 Nginx,在确认配置路径、运行前缀和 master 进程一致后重新加载:

nginx -s reload -c /etc/nginx/nginx.conf

若实例启动时还使用了自定义 -p,检查和重新加载也应使用相同前缀。查看加载结果与错误日志;新配置无法应用时,Nginx 会继续使用旧配置,所以服务仍可访问不等于修改已经生效。

随后核对入口请求的响应内容和后端日志:选择去前缀方案后,后端应看到 /users;选择保留方案后,应看到 /api/users。查询参数、认证以及需要保留的请求方法和请求体也应按真实调用场景验证,不能只用首页的 200 代替 API 验收。

如果 API 路由恢复了,但页面资源、跳转或登录仍异常,要分别检查应用的外部路径设置、重定向地址和 Cookie 路径。请求 URI 的替换并不会自动重写页面内全部链接,也不能替代这些应用配置。

出现新的路由或权限问题时,恢复本次修改前的配置,重新检查并加载,再复核原有接口。只回退此次相关变更,避免覆盖同期的其他配置修改。