本文由AI润色。

在配置 Nginx 反向代理时,经常能看到以下两种写法:

1
2
3
4
5
6
7
8
9
# 写法 A:末尾无 /
location /api/ {
    proxy_pass http://127.0.0.1:8080;
}

# 写法 B:末尾有 /
location /api/ {
    proxy_pass http://127.0.0.1:8080/;
}

末尾仅差一个 /,但转发到上游服务的实际路径截然不同。其核心差异在于 Nginx 处理 proxy_pass 时是否包含了 URI,进而决定了路径是“原样透传”还是“前缀替换”。

0.总览

文章总览

1. location 中的 /:请求匹配范围

location 用于定义请求 URL 的路由匹配规则。常见的前缀匹配行为如下:

  • location /:作为兜底匹配,捕获所有以 / 开头的请求。
  • location /api/:严格匹配以 /api/ 为前缀的请求路径。
请求路径location /api/ 是否匹配说明
/api/user匹配以前缀 /api/ 开头
/api/order/123匹配以前缀 /api/ 开头
/api/匹配精确匹配该目录
/api特殊处理默认不匹配,但见下方说明
/api123不匹配不属于 /api/ 路径
/user不匹配前缀不同

/api 与 /api/ 的关键区别:

  • location /api 是纯字符串前缀匹配,不仅能命中 /api/user,还会误伤 /api123。
  • location /api/ 限定了目录层级,不会匹配 /api123。

因此在代理整个接口前缀时,推荐显式指定尾部斜杠 location /api/。

注: 当 location /api/ 内部包含 proxy_pass 指令时,如果客户端直接请求 /api,Nginx 内部会触发隐式优化,自动返回 301 重定向到 /api/。

location /api/ 请求匹配范围示意图

2. proxy_pass 中的 /:路径透传与截断替换

Nginx 官方文档规定:如果 proxy_pass 指令中带有了 URI(哪怕只是一个 /),请求中与 location 匹配的部分就会被替换为指令中的 URI;如果未携带 URI,则保留原请求完整路径透传。

场景一:未指定 URI(完整保留原路径)

1
2
3
location /api/ {
    proxy_pass http://127.0.0.1:8080;
}

客户端访问 [http://example.com/api/user](http://example.com/api/user) 时: proxy_pass 只有 host:port,没有指定任何路径。Nginx 会将原始路径直接拼接到后端地址之后: [http://127.0.0.1:8080/api/user](http://127.0.0.1:8080/api/user)(原始 /api/ 得到保留)。

场景二:显式指定 URI 为 /(截断前缀)

1
2
3
location /api/ {
    proxy_pass http://127.0.0.1:8080/;
}

客户端同样访问 [http://example.com/api/user](http://example.com/api/user) 时: proxy_pass 指定了 URI 为 /,Nginx 执行字符串替换:

1
2
3
4
5
6
原始请求路径: /api/user
location 匹配部分:/api/
剔除匹配后剩余:user

替换前缀(URI):/
最终拼接路径:  / + user => /user

后端最终接收到的请求为:[http://127.0.0.1:8080/user](http://127.0.0.1:8080/user)。

proxy_pass 是否带 URI 的转发路径对比

3. proxy_pass 指定自定义 URI(路径重写)

替换逻辑并不局限于删除前缀,如果 proxy_pass 中指定了其他子路径,Nginx 会完成前缀映射:

1
2
3
location /api/ {
    proxy_pass http://127.0.0.1:8080/backend/;
}

当客户端请求 /api/user 时,计算流程如下:

1
2
3
4
5
6
原始路径:     /api/user
location 匹配:/api/
截取剩余部分:  user
proxy_pass URI:/backend/

最终路径:     /backend/ + user => /backend/user

计算逻辑可抽象为:

$$\text{转发路径} = \text{proxy_pass 指定的 URI} + (\text{原始请求路径} - \text{location 匹配的前缀})$$

自定义 URI 前缀映射流程图


4. 常见配置组合与“双斜杠”问题

假设客户端统一请求 [http://example.com/api/user](http://example.com/api/user):

location 配置proxy_pass 配置转发给后端的路径说明
/http://backend/api/user不带 URI,全量透传
/http://backend//api/user匹配 /,替换为 /,结果等同透传
/http://backend/v1//v1/api/user匹配 /,替换为 /v1/
/api/http://backend/api/user不带 URI,全量透传
/api/http://backend//user带 URI /,剥离 /api/ 前缀
/api/http://backend/v1//v1/user替换 /api/ 为 /v1/
/apihttp://backend/api/user不带 URI,全量透传
/apihttp://backend///user产生双斜杠(见下文分析)
/apihttp://backend/v1/v1/user匹配 /api,替换为 /v1,拼接余下的 /user
/apihttp://backend/v1//v1//user产生双斜杠

双斜杠 // 产生的原因

当配置如下时:

1
2
3
location /api {
    proxy_pass http://127.0.0.1:8080/;
}

客户端访问 /api/user:

  1. location /api 仅匹配了 /api 这 4 个字符。
  2. 剥离匹配部分后,剩余路径为 /user。
  3. proxy_pass 指定的前缀为 /。
  4. 拼接结果:/ + /user = //user。

虽然大部分 Web 框架与后端路由解析器具备路径归一化(Normalize)能力,会自动合并多余斜杠,但在 Nginx 层面应保持前缀规范,遵循“location 末尾带斜杠,proxy_pass 相应带斜杠”的书写习惯。

双斜杠产生原因与正确配置对照

5. Query 参数处理

Query 参数(即 URL ? 后的键值对)不受前缀替换影响,始终会被自动原样附带到最终请求后。

1
2
3
location /api/ {
    proxy_pass http://127.0.0.1:8080/backend/;
}

访问 /api/user?id=123&name=tom 时,后端直接收到 /backend/user?id=123&name=tom。

Query 参数保留示意图

6. 生产配置选型实践

根据后端接口设计,选用对应配置模板:

方案 A:后端接口天然自带 /api/ 前缀

后端服务的路由定义本身就是 /api/user,此时网关只需透传:

1
2
3
4
5
location /api/ {
    proxy_pass http://127.0.0.1:8080;
}

![Nginx 三种生产配置选型对比](https://img.326333.xyz/i/c0d834uec9Q6Mfz7BAyBc4TWP.webp)

方案 B:后端为独立服务,内部路径无 /api/

外部统一通过 /api/ 路由隔离,但内部服务实际路由为 /user:

1
2
3
location /api/ {
    proxy_pass http://127.0.0.1:8080/;
}

方案 C:前后端路由映射前缀不同

需要将外部 /api/ 统一映射到内部各微服务的 /internal/ 前缀:

1
2
3
location /api/ {
    proxy_pass http://127.0.0.1:8080/internal/;
}

7. 边界与限制

上述截断替换机制仅在普通前缀匹配 location中生效。以下场景行为不同:

  1. 正则 location 中禁止在 proxy_pass 中定义 URI: 在 location ~ ^/api/ 这类正则块中,proxy_pass 只能写到 ip:port(或 upstream 名),不能包含子路径(如 http://backend/ 会报错 nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression)。若需要改写路径,必须在块内搭配 rewrite ... break; 指令。
  2. rewrite 指令接管: 如果在普通 location 中显式调用了 rewrite ... break;,请求的 URI 已在 Nginx 内部完成了变更,proxy_pass 后续将直接转发改写后的路径,不再执行常规的前缀替换逻辑。

正则 location 与 rewrite break 边界规则