🚀 第06章 · location 与正则匹配

匹配优先级

location 与正则匹配

location 决定"某个请求到底走哪套处理逻辑"——是交给后端代理、直接从磁盘送文件、还是直接拦成 403。它是 Nginx 里最容易被理解偏差、也最容易出事故的指令。本章把 location 的书写和匹配优先级彻底讲清,并拿本机的拦截规则做实证。

location 语法

location [修饰符] URI {
    # 处理规则
}

URI 可以是一个路径前缀(如 /、/api/),也可以配合修饰符做精确或正则匹配。修饰符决定匹配强度,常见五种:

写法 含义 示例
= URI 精确匹配,URI 必须一字不差 location = /package.json
^~ URI 前缀匹配,一旦命中不再试正则 location ^~ /static/
~ URI 区分大小写的正则 location ~ ^/(node_modules\|scripts)/
~* URI 不区分大小写的正则 location ~* \.jpg$
(无) URI 普通前缀匹配 location /

五种匹配的优先级

这是本章的重中之重。Nginx 挑选 location 的顺序:

  1. 先找 = 精确匹配;
  2. 再找 ^~ 前缀匹配——若命中,直接定案,不再进行下一步;
  3. 再在所有正则匹配里,按书写顺序找到第一个匹配的 ~/~*;
  4. 若还没有匹配,退回到长度最长的普通前缀匹配;
  5. 若连普通前缀都没有,location /(0 长度,最宽松)兜底。

理解 很多教程说"正则优先于前缀"——那是在同属普通前缀的前提下。一旦 = 精确或 ^~ 前缀命中,正则就到不了这一步。所以准确排序是:= > ^~ > 正则 > 普通前缀(最长) > /。

本机 location 实证

回看 bangdream.xyz.conf,它使用了全部三类:

# 1) 正则:拦截 node_modules / scripts 目录
location ~ ^/(node_modules|scripts)/ { return 403; }

# 2) 精确:拦截包清单文件
location = /package.json { return 403; }
location = /package-lock.json { return 403; }

# 3) 普通前缀:其余交给后端
location / {
    proxy_pass http://127.0.0.1:3000;
}

某个请求到达后,Nginx 会这样裁决:

请求 URI 经过的判定 结果
/package.json 命中 = 精确 403,不透传
/node_modules/vue/index.js 没有 =;正则 ^/(node_modules\|scripts)/ 命中 403,不透传
/scripts/build.sh 同上,正则命中 403,不透传
/main.js = 未中;正则未中;走普通前缀 / 转发到 127.0.0.1:3000

理解 注意 /scripts/build.sh 这类请求:前缀 / 本来能匹配它,但因为是普通前缀,优先级低于正则,所以先被正则 location ~ ^/(node_modules|scripts)/ 抢到并 return 403。这正是"防泄露规则必须优先于透传规则"的根因。

= 与 ^~ 与普通前缀的区别

三者都算"前缀类",但行为不同,是最容易混淆的地方:

场景 用哪种 为什么
只匹配一个精确文件 = /package.json 一字不差,最省事,不误伤
整个目录不能再被正则干扰 ^~ /static/ 命中即止,跳过正则遍历
常规路径分发 普通 /api/ 与其它 location 一起比较长度
按模式模糊拦截 ~ / ~* 需要正则时才用

易错 location = /package-lock.json 这里如果不写 = 而写成普通 location /package-lock.json,就会变成一个前缀匹配——于是 /package-lock.json.bak 这类请求也会命中它,虽然本意只想拦那个确切文件。精确就用 =,前缀就明写前缀,别混。

常见组合模式

静态目录,规则别被正则抢走

location ^~ /static/ {
    alias /var/www/mydemo/static;
    expires 7d;
}

用 ^~ 声明:一旦命中 /static/,直接送文件,不再往下试探任何正则。若前面某处还写了 location ~* \.(css|js)$,这个 ^~ 也能稳稳接管 /static/ 里的资源。

图片按扩展名统一规则

location ~* \.(jpg|jpeg|png|gif|webp|svg|ico)$ {
    alias /var/www/mydemo/img;
    expires 30d;
}

~* 不区分大小写,Logo.PNG 也能命中。

易错 正则是按配置书写顺序依次找"第一个"匹配的。若两个正则都能命中同一个请求,前一个生效,后面的被无视。所以更具体、更精确的正则要写在前面,宽松的放后面。

location 与 proxy_pass 路径拼接

前面讲过的"有没有 /"规则,在 location 里最易出错。回顾:

location /api/ {
    proxy_pass http://127.0.0.1:3000;      # 无斜杠:原样,后端收到 /api/user
}

location /api/ {
    proxy_pass http://127.0.0.1:3000/;     # 有斜杠:替换,后端收到 /user
}

易错 判断"会不会削路径"只看 proxy_pass 末尾——有斜杠会削掉 location 匹配的前缀,没斜杠原样透传。在正则 location 里用带斜杠的 proxy_pass 会报错,因为 Nginx 无法确定"该替换哪一段";这种场景必须用变量或拆成前缀 location。

调试 location 匹配

# 观察实际走了哪个 location(启用 debug 日志,或在 location 里加头)
location /debug { return 200 "here"; }

更推荐用 var 加自定义头测试:

# 在 location 里加一行
add_header X-Matched-Location "proxy-to-3000";

然后:

curl -I https://bangdream.xyz/main.js
# 看响应头里 X-Matched-Location 的值,即知命中了哪个 location

理解 最直接的验证视角是"加一个能自证身份的响应头"。改完 nginx -t + reload,再 curl -I 观察这个头,秒懂请求到底去了哪套逻辑。

小结

location 的匹配优先级是 = 精确 > ^~ 前缀 > ~/~* 正则(按顺序取第一个) > 普通前缀(取最长)> / 兜底。本机的防泄露规则正是利用"正则优先于普通前缀 /",把敏感路径在透传前拦成 403。写 location 时先问:我要的是精确、还是前缀、还是正则?并保证"更严格的规则写在前面/优先级更高"。下一章讲如何在 location 基础上做访问控制与限流。