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

Nginx 报 proxy_pass cannot have URI part

Nginx 正则 location 中,固定 proxy_pass 地址末尾的斜杠也属于 URI,可能导致配置检查失败。按后端需要保留完整路径,或先用 rewrite 改写,再通过不带 URI 的地址转发。

给 location 加上正则表达式后,Nginx 在配置检查或启动阶段报错:

nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression

后面还可能列出 named location、if 或 limit_except 等上下文。这里讨论正则 location 中使用固定上游地址的情况:proxy_pass 后面除了协议、主机和端口,还附带了一个 URI,Nginx 无法按普通前缀匹配的规则完成替换。

端口后面即使只有一个 / 也属于 URI。下面这个配置就会触发问题:

location ~ ^/api/(v1|v2)/ {
    proxy_pass http://127.0.0.1:9000/;
}

示例中的上游是 127.0.0.1:9000,表示 Nginx 所在网络环境里的本机服务。实际部署要换成 Nginx 能访问的上游地址;容器里的回环地址并不指向宿主机。

区分上游地址与后面的 URI

下面两个值看起来只差一个字符,但配置含义不同:

proxy_pass http://127.0.0.1:9000;
proxy_pass http://127.0.0.1:9000/;

这是两个用于比较的写法,不要把它们同时放进同一个 location。第一行没有 URI 部分;第二行附带 URI /。如果写成 http://127.0.0.1:9000/internal/,URI 就是 /internal/,同样不能作为固定 URI 直接放进这里的正则匹配配置。

普通前缀 location 可以确定请求中哪一段前缀需要替换。正则表达式可能带分组、可选部分或多个分支,不能套用同一种前缀替换规则。因此,这条错误发生在解析配置时,还没有进入连接后端、检查接口是否存在的阶段。

按报错中的文件和行号找到配置,再查看外层的 location 是否使用了 ~ 或 ~*。不要因为地址中有后端端口,就转去调整防火墙或代理超时。

后端需要完整路径时,去掉 URI 部分

如果客户端请求 /api/v1/users?id=7,而后端本来就有 /api/v1/users 这个接口,可以保留正则匹配,只让 proxy_pass 指定上游地址:

location ~ ^/api/(v1|v2)/ {
    proxy_pass http://127.0.0.1:9000;
}

在没有其他 URI 改写的前提下,请求仍以 /api/v1/users?id=7 发给后端。这里删除末尾斜杠,不等于删除客户端路径中的 /api/v1/。

如果这样修改后配置检查通过,但接口变成 404,应查看后端收到的请求路径。后端只提供 /users 时,它接到 /api/v1/users 当然可能匹配不到路由。此时还需要明确的路径改写,不能把末尾斜杠再加回去。

后端需要去掉前缀时,先 rewrite 再代理

假设 /api/v1/ 和 /api/v2/ 都是代理层提供的入口,后端实际接收 /users、/orders 等路径,可以在同一个正则 location 中明确改写:

location ~ ^/api/(v1|v2)/ {
    rewrite ^/api/(v1|v2)/(.*)$ /$2 break;
    proxy_pass http://127.0.0.1:9000;
}

第二个捕获组保存版本前缀之后的路径,替换成 /$2 后,/api/v1/users 变为 /users。原有查询参数仍保留,因此 /api/v1/users?id=7 对应 /users?id=7。这个示例没有在替换串里增加新参数,也没有使用结尾问号来丢弃旧参数。

这里用 break 结束当前这组改写,并继续使用当前 location 的代理配置。不要随意换成 last:last 会根据修改后的 URI 重新查找 location,可能把请求送到另一个处理块。

proxy_pass 仍不带 URI;它会把改写后的完整 URI 传给上游。Nginx 1.1.12 之前,无 URI 的代理在部分场景下可能传递原始 URI 而非改写后的 URI,不应依赖这种旧版本行为。

是否应该去掉版本前缀,必须由后端接口约定决定。如果后端要区分 /api/v1/ 和 /api/v2/,就不能用这个示例把两者都折叠成相同路径。正则也刻意要求版本后有 /,不匹配 /api/v10/ 或没有尾部斜杠的 /api/v1;这些入口要按应用需要单独安排。

若原需求只是一个固定路径前缀,并不需要正则,可以改用普通前缀 location,再按其 URI 替换规则配置。不过调整匹配方式时,要同时检查其他正则、精确匹配和前缀规则,避免请求落入不同的处理块。

不要为绕过报错随意引入变量

带变量的 proxy_pass 有另一套 URI 处理规则。它并不等同于“把固定地址拼成字符串”:附带的 URI 可能直接替换原请求 URI,域名解析还可能需要额外的 resolver 配置。

因此,不建议仅为了消除报错,就临时加一个变量或把整段地址改成拼接表达式。先确定上游究竟应收到完整路径还是改写后的路径,再选择对应的写法。已有变量代理配置需要按变量展开后的地址、URI 和解析方式单独检查,不能直接套用本文的固定地址结论。

检查实际配置和上游收到的路径

修改前保存目标配置文件的副本,保留原有 TLS、认证、请求头、上传限制等与此次问题无关的设置。以上都是放在现有 server 内的 location 示例,不能代替整个站点配置。

在与部署版本和配置一致的本地环境先检查:

nginx -t

如果实例启动时使用了自定义配置路径,应给检查命令使用相同路径,而不是测试另一个默认文件:

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

路径需要替换为该实例实际使用的主配置路径。需要检查 include 展开后的完整内容时,可以使用 nginx -T;输出可能包含内部地址或敏感配置,不应直接公开粘贴。-T 从 Nginx 1.9.2 起提供。

配置检查会验证语法并尝试打开配置引用的文件,但不会替你验证后端路由。还要在本地候选环境发送一条可安全重复的请求,并查看上游实际收到的路径与参数。按本次选择,应该看到完整的 /api/v1/users?id=7,或改写后的 /users?id=7,不能只凭 HTTP 状态码判断改写正确。

如果接口涉及 POST、上传或认证,还需用对应场景核对方法、请求体和认证信息是否正常传递。URL 路径改写不负责自动修复后端返回的跳转地址、Cookie 路径或页面里的绝对资源地址;这些问题需要按应用的外部访问前缀分别处理。

本地检查通过后,通过既有部署流程更新目标实例。对直接管理的 Nginx 实例,可在确认操作的是同一个 master 及其配置后重新加载:

nginx -s reload

新配置如果无法应用,Nginx 会继续使用旧配置;因此网站还能访问,并不能证明新配置已经生效。检查重新加载的结果和错误日志,再验证新请求的路由。

如果路由结果不符合预期,恢复本次配置副本,重新检查并加载。不要为了一条 URI 配置错误直接停止仍在工作的实例,也不要在回退时覆盖其他人同期修改的站点配置。