5月28日 07:26

Nginx 的 location 指令如何匹配?优先级是什么?

Nginx 的 location 指令如何匹配?优先级是什么?

location 是 Nginx 中最核心的指令之一,它决定了一个请求由哪个配置块来处理。理解它的匹配规则和优先级,不仅是面试高频考点,更是排查 Nginx 配置问题的基本功。

location 的四种匹配方式

location 指令的语法:location [=|~|~*|^~] uri { ... }

修饰符不同,匹配行为完全不同:

修饰符匹配方式匹配后是否继续搜索
=精确匹配否,立即停止
^~前缀匹配否,跳过正则检查
~正则匹配(区分大小写)否,按配置顺序首个命中即停止
~*正则匹配(不区分大小写)否,按配置顺序首个命中即停止
前缀匹配是,继续检查正则

精确匹配(=)

只有请求 URI 与指定路径完全一致时才命中,一旦匹配立即停止搜索,性能最优:

nginx
location = / { # 仅匹配 /,不匹配 /index.html }

前缀匹配(无修饰符)

按 URI 前缀匹配,匹配成功后不会立即使用,而是先记住这个最长前缀匹配,继续检查正则表达式。如果正则没有命中,才会回退使用这个前缀匹配:

nginx
location /docs/ { # 匹配以 /docs/ 开头的所有 URI # 但如果有正则也命中了,正则优先 }

正则匹配(~ 和 ~*)

  • ~:区分大小写
  • ~*:不区分大小写

正则匹配按配置文件中出现的顺序依次检查,首个命中即停止:

nginx
location ~ \.php$ { # 区分大小写,匹配 .php 结尾的请求 } location ~* \.(jpg|png|gif|css|js)$ { # 不区分大小写,匹配常见静态资源 }

此外还有 !~!~*,表示正则不匹配,但它们不能用于 location 指令,只能用在 if 条件判断中。

前缀匹配(^~)

行为与无修饰符的前缀匹配类似,但关键区别是:如果 ^~ 前缀匹配成功,会跳过后续所有正则检查,直接使用该 location。这在对性能敏感的场景下非常有用:

nginx
location ^~ /static/ { # 匹配 /static/ 开头的请求 # 即使有正则也匹配,也不检查,直接用这个 }

Nginx 完整匹配算法

面试中光背优先级顺序不够,必须理解 Nginx 的完整匹配流程:

  1. Nginx 首先检查所有前缀匹配(包括 =^~ 和无修饰符),找到最长前缀匹配
  2. 如果最长前缀是精确匹配(=),立即使用,匹配结束
  3. 如果最长前缀是 ^~ 匹配,立即使用,匹配结束
  4. 按配置文件顺序依次检查所有正则表达式(~~*
  5. 如果正则命中,使用该正则 location,匹配结束
  6. 如果所有正则都未命中,回退使用步骤 1 中找到的最长前缀匹配

这个流程说明一个关键点:正则匹配的优先级高于普通前缀匹配,但低于 =^~

优先级总结(从高到低)

  1. 精确匹配 = — 最高优先级,匹配即停
  2. 前缀匹配 ^~ — 匹配后跳过正则检查
  3. 正则匹配 ~ / ~* — 按配置顺序,先到先得
  4. 普通前缀匹配 — 优先级最低,作为兜底

注意:正则匹配之间没有优先级之分,完全取决于配置文件中的书写顺序,写在前面的先匹配。

配置示例与匹配结果

nginx
server { listen 80; server_name example.com; location = / { return 200 "1: exact /"; } location / { return 200 "5: prefix /"; } location ^~ /images/ { return 200 "2: ^~ /images/"; } location ~ \.php$ { return 200 "3: regex .php"; } location ~* \.(jpg|png|gif)$ { return 200 "4: regex image"; } location /docs/ { return 200 "6: prefix /docs/"; } }

匹配结果验证:

请求 URI命中 location原因
/= /精确匹配,优先级最高
/images/logo.jpg^~ /images/^~ 命中后跳过正则
/api/test.php~ \.php$前缀匹配 / 记住后,正则命中
/photo.JPG~* \.(jpg|png|gif)$不区分大小写正则命中
/docs/readme.html/docs/最长前缀 /docs/ 优先于 /,且无正则命中
/about/仅前缀 / 命中,无正则匹配

常见面试陷阱

陷阱一:前缀匹配的长度优先

多个普通前缀同时匹配时,Nginx 选择最长前缀,而非配置顺序:

nginx
location /api/ { ... } # 前缀长度 5 location /api/v1/ { ... } # 前缀长度 9,优先

请求 /api/v1/users 会匹配 /api/v1/,因为前缀更长。

陷阱二:正则覆盖普通前缀

nginx
location /images/ { ... } location ~* \.(jpg|png)$ { ... }

请求 /images/logo.jpg 会命中正则 ~*,而非前缀 /images/,因为正则优先级高于普通前缀。如果希望 /images/ 下的请求不被正则抢走,必须使用 ^~

陷阱三:= 只匹配精确路径

nginx
location = /api { ... }

它只匹配 /api,不匹配 /api//api/v1。如果需要同时匹配,应该用前缀匹配。

实际配置建议

  1. 高频路径用精确匹配 =,性能最优且语义清晰
  2. 静态资源目录用 ^~,避免被正则拦截
  3. 正则匹配控制数量,按命中频率从高到低排列
  4. 避免在正则中使用复杂回溯表达式,防止 ReDoS 攻击
  5. 通用兜底 location / 放在最后
nginx
# 精确匹配首页 location = / { proxy_pass http://frontend; } # 静态资源,跳过正则检查 location ^~ /static/ { alias /var/www/static/; expires 30d; } # PHP 请求转发 location ~ \.php$ { fastcgi_pass unix:/run/php/php-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # 管理后台,跳过正则检查 location ^~ /admin/ { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://admin_backend; } # API 代理 location /api/ { proxy_pass http://api_backend; } # 兜底 location / { try_files $uri $uri/ /index.html; }

追问:location 嵌套怎么用?

location 支持嵌套,但只有普通前缀匹配可以嵌套在普通前缀内,正则和精确匹配不能嵌套:

nginx
location /api/ { proxy_pass http://backend; location /api/internal/ { # 更具体的路径,覆盖外层配置 deny all; } }

内层 location 会完全覆盖外层的处理逻辑,而不是继承。如果内层也需要 proxy_pass,必须显式重新声明。

追问:location 和 rewrite 的执行顺序?

Nginx 处理请求时,rewrite 阶段在 location 匹配之前执行(server 级别的 rewrite)。如果 rewrite 修改了 URI,location 会基于修改后的 URI 重新匹配。但 location 内部的 rewrite 可能触发重新匹配,需要注意避免循环重写。

标签:Nginx