跳到主要内容

Nginx 配置最爱坑人的两件事:location 匹配优先级和 proxy_pass 末尾斜杠

一个 404 查了半小时,最后发现是 proxy_pass 多写了一个斜杠,这是 Nginx 反向代理最经典的坑。这篇把 location 四种匹配的真实优先级、proxy_pass 带不带斜杠的路径改写规则讲透,附可直接复现的配置和请求例子。

发布于

Nginx 配置最爱坑人的两件事:location 匹配优先级和 proxy_pass 末尾斜杠

按 W3Techs 的 Web 服务器市场统计,nginx 长期为全球约三分之一的网站提供服务(2024 年约 33.8%),装机量这么大,意味着每天都有无数人在写 nginx 配置,也就意味着每天都有无数人掉进同样的两个坑:location 到底命中了哪一条,以及 proxy_pass 末尾那个斜杠到底改不改路径。这两个问题加起来,大概能解释一半以上"配置看着没问题但返回 404/502"的工单。这篇文章把规则一次讲清,例子都可以直接复制到你的 server 块里复现。

location 匹配:写在前面的不一定先命中

很多人凭直觉认为 nginx 按配置文件里的书写顺序逐条尝试 location,匹配到第一条就停。这个直觉对正则部分成立,对其它部分完全不成立。真实的优先级是固定的四档:

  1. location = /exact:精确匹配,命中立刻结束;
  2. location ^~ /static/:优先前缀,命中后跳过所有正则;
  3. location ~ \.php$ / ~* \.(jpg|png)$:正则,按书写顺序,第一条命中即停;
  4. location /api/:普通前缀,取最长的那条,且只在没有正则命中时兜底。

注意第 2 档和第 4 档:^~ 和普通前缀写法只差两个字符,行为却差一整档。一个常见事故是这样的配置:

location /images/ {
    root /var/www;
}
location ~* \.(jpg|png)$ {
    expires 30d;
    root /data/cdn;
}

请求 /images/logo.png 时,普通前缀 /images/ 虽然匹配,但正则档优先级更高,最终命中的是第二条,文件从 /data/cdn 找:如果图片实际在 /var/www/images/ 下,你就得到一个莫名其妙的 404。把第一条改成 location ^~ /images/ 才能按预期短路掉正则。我把这套优先级整理进了 Nginx 速查表 的 location 分类里,每条都标了对应的坑,记不住的时候搜 "location" 比翻官方文档快得多。

proxy_pass 的斜杠:一个字符决定后端收到什么路径

规则其实只有一句话:proxy_pass 的 URL 里只要带了路径部分(哪怕只是一个 /),nginx 就会把 location 匹配掉的前缀替换成这个路径;不带路径,则原样透传整个 URI。 但这句话太抽象,直接看输入输出。

配置 A(带斜杠):

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

请求 GET /api/users/42 → 后端 Node 服务收到的是 GET /users/42(前缀 /api/ 被替换成 /)。

配置 B(不带斜杠):

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

同样请求 GET /api/users/42 → 后端收到的是原样的 GET /api/users/42

如果你的后端路由注册的是 /users/:id,配置 B 会让每个请求都 404;反过来,后端如果本来就挂在 /api 下,配置 A 会把前缀剥掉,同样全线 404。更阴险的变体是 proxy_pass http://127.0.0.1:3000/v1;(路径不以斜杠结尾):请求 /api/users 会被拼成 /v1users,中间连斜杠都没有。

我在一台新 VPS 上实测的排查流程

上周我在一台刚开通的 VPS 上把一个 Express 应用挂到 nginx 后面,用的就是配置 B 的写法,后端路由注册在 /users,于是浏览器里全是 404。我的排查顺序是:先跑 nginx -t 确认语法,它输出 syntax is oktest is successful 两行,说明问题不在语法层;再在服务器上用 curl -v http://127.0.0.1:3000/users/42 直连后端,确认返回 200,锁定问题出在 nginx 改写路径这一环;最后在 Express 里加了一行 console.log(req.url),打出来的是 /api/users/42,真相大白:proxy_pass 没带斜杠,前缀被原样透传了。整个过程里 curl 的 -v 参数(看请求行和响应头)是关键,这类直连验证的命令组合我都是从 curl 命令速查 里现查的,比凭记忆拼参数稳。

补一个判断方向的小技巧:同样是 404,nginx 自己产生的 404 和后端返回的 404 长得不一样。默认情况下 nginx 的 404 页面带 <center>nginx</center> 落款,而 Express 的 404 是 Cannot GET /xxx 纯文本。响应是谁发的,决定你该查 location 还是查 proxy_pass。各个状态码的语义和典型成因可以在 HTTP 状态码速查 里对着查,502 和 504 的区别(连不上后端 vs 后端超时)在排反代问题时尤其常用。

改完配置之后:reload 而不是 restart

最后一个小但高频的坑:验证配置和应用配置是两步。正确顺序永远是先 nginx -t,通过后再 nginx -s reload。reload 是平滑重载,老 worker 处理完存量连接才退出,线上不掉请求;而 systemctl restart nginx 会硬断连接,且如果新配置有错,服务直接起不来,等于把一个配置问题升级成一次可用性事故。nginx -t 通过并不代表万事大吉(它只查语法和文件是否存在,查不出 proxy_pass 斜杠这种语义问题),所以 reload 之后用 curl 把核心路径打一遍,才算闭环。

location 优先级四档、proxy_pass 一个斜杠、reload 两步走,这三件事记住了,nginx 反代配置里八成的诡异问题都能在十分钟内定位。剩下两成,搜速查表里"毁掉你一天的七个报错"那一栏。


Made by Toolora · Updated 2026-06-13