Nginx location 匹配规则详解

本文逐段对照 Nginx 官方文档,系统讲解 location 的匹配顺序:普通 location 采用"最大前缀匹配",正则 location 按配置文件中的物理顺序匹配且命中即止;普通 location 先于正则 location 匹配,正则 location 会"覆盖"普通 location 的最大前缀匹配结果,但会"让步"于普通 location 的严格精确匹配。文中还逐一说明了前缀 =、^~、~、~*、@ 的含义与区别,并通过多个可复现的配置例题推演完整的匹配决策过程。


一、关于 location 认识的常见误区

在正式讲解之前,先纠正两个流传很广的错误认识。

误区一:location 的匹配顺序是"先匹配正则,再匹配普通"

矫正:location 的匹配顺序其实是"先匹配普通,再匹配正则"。

很多人会反驳这一点,因为按"先匹配普通,再匹配正则"似乎解释不了平时"先匹配正则,再匹配普通"的实践经验。造成这种误解的原因是:正则匹配会覆盖普通匹配(实际规则比这更复杂,后文会详细解释)。正是这种"覆盖"现象,让人误以为正则先执行。

误区二:location 的执行逻辑与 location 的编辑顺序无关

矫正:这句话不全对。

  • "普通 location"的匹配规则是"最大前缀匹配",因此普通 location 的确与编辑顺序无关;
  • "正则 location"的匹配规则是"顺序匹配,且只要匹配到第一个就停止后面的匹配",因此正则 location 与编辑顺序密切相关。

普通 location 与正则 location 之间的顺序是:先匹配普通 location,再"考虑"匹配正则 location。注意这里的"考虑"是"可能"的意思——匹配完普通 location 后,有时需要继续匹配正则 location,有时则不需要。

以下两种情况不需要继续匹配正则 location:

  1. 当普通 location 前面指定了 ^~,明确告诉 Nginx:本条普通 location 一旦匹配上,就不需要继续正则匹配;
  2. 当普通 location 恰好严格匹配上(是精确匹配,而非最大前缀匹配),则不再继续匹配正则。

一句话总结:正则 location 匹配让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果。


二、官方 location 语法

官方文档(http://wiki.nginx.org/NginxHttpCoreModule#location)对 location 的定义如下:

syntax:  location [=|~|~*|^~|@] /uri/ { ... }
default: no
context: server

原文:

This directive allows different configurations depending on the URI.

译文:该指令允许根据 URI 的不同使用不同的配置。这里有两点需要注意:

  1. 所谓"根据 URI 使用不同配置",指的就是语法 location [=|~|~*|^~|@] /uri/ { ... }。依据不同的前缀 =、^~、~、~* 以及不带任何前缀([A] 表示可选),表达不同的含义。简单说,尽管 /uri/ 配置一样,但前缀不同,指令含义就不同。
  2. 查询字符串不在 URI 范围内。例如 /films.htm?fid=123 的 URI 是 /films.htm。

三、普通 location 与正则 location

原文:

It can be configured using both literal strings and regular expressions. To use regular expressions, you must use a prefix:

  • "~" for case sensitive matching
  • "~*" for case insensitive matching

译文:location 可以使用字面量字符串(literal strings)和正则表达式(regular expressions)两种方式配置。使用正则表达式必须带前缀:

  • ~:区分大小写的正则匹配;
  • ~*:不区分大小写的正则匹配。

按照前缀分类,location 只有两大类:

类别 英文说法 包含的前缀
普通 location location using literal strings =、^~、@,以及不带任何前缀的 /uri/
正则 location location using regular expressions ~、~*

其中 ~ 区分大小写,~* 不区分大小写。


四、匹配顺序与决策

4.1 先匹配普通 location

原文:

To determine which location directive matches a particular query, the literal strings are checked first.

译文:对于一个特定的 HTTP 请求,Nginx 应该匹配哪个 location 块?规则是:先匹配普通 location,再匹配正则表达式。官方文档这句话明确说了"先普通 location",而不是有些同学误以为的"先匹配正则 location"。

4.2 普通 location 内部:最大前缀匹配

原文:

Literal strings match the beginning portion of the query – the most specific match will be used.

译文:普通 location 匹配的是 URI 的前缀部分(beginning portion),并采用"最具体"的匹配(the most specific match),即最大前缀匹配。

因为普通 location 是"前缀匹配"而非"严格匹配",一个 HTTP 请求可能同时前缀匹配到多个普通 location。例如:

location /prefix/mid/ { ... }
location /prefix/ { ... }

对于请求 /prefix/mid/t.html,两个 location 都满足前缀匹配,此时按 the most specific match 原则,选择更长、更具体的 location /prefix/mid/ { ... }。

4.3 正则 location 内部:按编辑顺序匹配,命中即止

原文:

Afterwards, regular expressions are checked in the order defined in the configuration file. The first regular expression to match the query will stop the search.

这段话包含两层意思:

  1. 普通 location 先匹配,并且选出最大前缀匹配后,并不能就此停止——最大前缀匹配只是一个临时结果,Nginx 还需要继续检查正则 location。
  2. 正则 location 之间的匹配规则是:按照其在配置文件中的物理顺序(编辑顺序)匹配,只要匹配到第一条,就停止后续搜索。

这说明 location 并非一定与顺序无关:普通 location 与顺序无关,但正则 location 与顺序有关。

4.4 普通结果与正则结果的决策关系

原文:

If no regular expression matches are found, the result from the literal string search is used.

这句话回答了"普通 location 的最大前缀匹配结果"与"正则 location 匹配结果"之间的决策关系:

  • 如果继续搜索的正则 location 有匹配上的,则正则 location 覆盖普通 location 的最大前缀匹配结果(正是这种覆盖关系,造成了"正则 location 先于普通 location 执行"的误解);
  • 如果正则 location 没有匹配上,则使用普通 location 的最大前缀匹配结果。

4.5 其他补充说明

原文:

For case insensitive operating systems, like Mac OS X or Windows with Cygwin, literal string matching is done in a case insensitive way (0.7.7). However, comparison is limited to single-byte locale's only.

Regular expression may contain captures (0.7.40), which can then be used in other directives.

译文:

  • 在不区分大小写的操作系统上(如 Mac OS X 或带 Cygwin 的 Windows),字面量字符串匹配也是不区分大小写的(0.7.7 起),但比较仅限于单字节 locale。
  • 正则表达式可以包含捕获组(captures,0.7.40 起),捕获的结果可在其他指令中使用。

五、匹配前缀分类详解

5.1 前缀总览表

前缀 类型 含义 是否继续匹配正则
(无前缀) 普通 location 最大前缀匹配 是(除非恰好为精确匹配)
= 普通 location 严格精确匹配(exact match) 否,命中即停止
^~ 普通 location 最大前缀匹配,命中后不再检查正则 否
~ 正则 location 区分大小写的正则匹配 命中即停止,不再检查后续正则
~* 正则 location 不区分大小写的正则匹配 命中即停止,不再检查后续正则
@ 命名 location 仅用于内部重定向,不处理普通请求 不参与常规匹配

5.2 ^~ 前缀

原文:

It is possible to disable regular expression checks after literal string matching by using "^~" prefix. If the most specific match literal location has this prefix: regular expressions aren't checked.

通常的规则是:匹配完普通 location 后,还需要继续匹配正则 location。但可以在普通 location 前加上 ^~ 告诉 Nginx:匹配到本条普通 location 后,不再继续匹配正则 location。

符号含义可直观理解为:^ 表示"非",~ 表示"正则",合起来即"不要继续匹配正则"。需要注意的是,^~ 依然遵守"最大前缀"匹配规则。

5.3 = 前缀

原文:

By using the "=" prefix we define the exact match between request URI and location. When matched search stops immediately. E.g., if the request "/" occurs frequently, using "location = /" will speed up processing of this request a bit as search will stop after first comparison.

= 前缀定义请求 URI 与 location 之间的严格精确匹配(exact match),命中后搜索立即停止。

^~ 和 = 都能阻止继续搜索正则 location,它们的区别在于:

  • 共同点:都能阻止继续搜索正则 location;
  • 不同点:^~ 依然遵守"最大前缀"匹配规则;而 = 不是最大前缀匹配,必须是严格精确匹配。

location / {} 与 location = / {} 的区别

  • location / {} 遵守普通 location 的最大前缀匹配。由于任何 URI 都必然以 / 根开头,所以对于一个 URI,如果有更具体(specific)的匹配,自然选更具体的;如果没有,/ 一定能"垫背"(至少能匹配到 /)。因此 location / {} 有点"默认配置"的味道,其他更具体的配置能覆盖(overwrite)这个默认配置——这也是为什么配置中总能看到 location / {}。
  • location = / {} 遵守严格精确匹配,只能匹配 http://host:port/ 这一个请求,同时会禁止继续搜索正则 location。如果只想对 GET / 请求配置指令,使用 location = / {} 能减少正则 location 的搜索,效率比 location / {} 更高(前提是目的仅仅是对 GET / 起作用)。

5.4 隐式的精确匹配

原文:

On exact match with literal location without "=" or "^~" prefixes search is also immediately terminated.

除了在普通 location 前显式加 ^~ 或 = 来阻止正则搜索外,还有一种"隐含"方式:当"最大前缀"匹配恰好就是一个"严格精确匹配(exact match)"时,照样会停止后面的搜索。也就是说,只要遇到精确匹配,即使普通 location 没有带 = 或 ^~ 前缀,也会终止后续匹配。

先举例说明(后面的例题会用实践验证):

location /exact/match/test.html { 配置指令块1 }
location /prefix/ { 配置指令块2 }
location ~ \.html$ { 配置指令块3 }
  • 请求 GET /prefix/index.html:会匹配到指令块 3。因为普通 location /prefix/ 依据最大前缀原则能匹配当前请求,但会被后面的正则 location 覆盖;
  • 请求 GET /exact/match/test.html:会匹配到指令块 1。因为这是普通 location 的 exact match,会禁止继续搜索正则 location。

5.5 官方匹配顺序总结

原文:

To summarize, the order in which directives are checked is as follows:

  1. Directives with the "=" prefix that match the query exactly. If found, searching stops.
  2. All remaining directives with conventional strings. If this match used the "^~" prefix, searching stops.
  3. Regular expressions, in the order they are defined in the configuration file.
  4. If #3 yielded a match, that result is used. Otherwise, the match from #2 is used.

官方给出的匹配检查顺序如下:

  1. = 前缀的严格精确匹配,命中则停止搜索;
  2. 所有剩余的普通字符串(conventional strings)匹配。如果该匹配带 ^~ 前缀,则停止搜索;
  3. 正则表达式,按其在配置文件中定义的顺序匹配;
  4. 如果第 3 步有匹配,使用第 3 步结果;否则使用第 2 步的结果。

用自己的话概括仍然是那句:正则 location 匹配让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果。

5.6 URI 是解码后比较的

原文:

It is important to know that nginx does the comparison against decoded URIs. For example, if you wish to match "/images/ /test", then you must use "/images/ /test" to determine the location.

Nginx 是对解码后的 URI 进行比较的。浏览器地址栏显示的 URL 一般会进行 URLEncode,例如"空格"会被编码为 %20,但 Nginx 的 URL 匹配针对的是 URLDecode 之后的结果。也就是说,如果要匹配 /images/ /test(其中包含一个空格),写 location 时匹配目标应写解码后的 /images/ /test。

5.7 @ 前缀:命名 location

原文:

The prefix "@" specifies a named location. Such locations are not used during normal processing of requests, they are intended only to process internally redirected requests (see error_page, try_files).

@ 前缀用来定义"Named Location"(命名 location)。可以把它理解为独立于"普通 location"和"正则 location"之外的第三种类型。这种命名 location 不用于处理普通的 HTTP 请求,而是专门用来处理"内部重定向(internally redirected)"请求。

这里的"内部重定向"说成 "forward" 可能更好理解:它不需要与浏览器交互,纯粹是服务端的一次转发行为(配合 error_page、try_files 使用)。


六、官方示例解析

6.1 四个 location 配置

官方给出的示例配置如下:

location = / {
    # matches the query / only.
    [ configuration A ]
}

location / {
    # matches any query, since all queries begin with /, but regular
    # expressions and any longer conventional blocks will be
    # matched first.
    [ configuration B ]
}

location ^~ /images/ {
    # matches any query beginning with /images/ and halts searching,
    # so regular expressions will not be checked.
    [ configuration C ]
}

location ~* \.(gif|jpg|jpeg)$ {
    # matches any request ending in gif, jpg, or jpeg. However, all
    # requests to the /images/ directory will be handled by
    # Configuration C.
    [ configuration D ]
}

关于 location / { [configuration B] } 的注释需要特别澄清。原文注释:

matches any query, since all queries begin with /, but regular expressions and any longer conventional blocks will be matched first.

大意是:location / {} 能匹配所有 HTTP 请求,因为任何请求都以 / 开始(这半句没错);但是正则 location 和其他任何比 / 更长的普通 location(location / {} 是普通 location 里最短的,其他普通 location 都比它长,当然 location = / {} 和 location ^~ / {} 与它一样长)会"优先匹配(matched first)"。

原文作者说 "regular expressions will be matched first" 只是想表达"正则 location 会覆盖这里的 location / {}",但实际过程依然是普通 location / 先匹配,接着正则 location 再匹配;而其他更长的普通 location(any longer conventional blocks)的确会先于 location / {} 匹配。这个简化的措辞容易让人误以为"先检查正则 location,再检查普通 location"。

6.2 请求匹配结果

/                          -> configuration A
/documents/document.html   -> configuration B
/images/1.gif              -> configuration C
/documents/1.jpg           -> configuration D

逐一分析:

  • /:精确命中 location = /,使用 configuration A,停止搜索;
  • /documents/document.html:普通 location 最大前缀匹配到 /(configuration B),且没有正则命中(.html 结尾不匹配 \.(gif|jpg|jpeg)$),使用 B;
  • /images/1.gif:普通 location 最大前缀匹配到 ^~ /images/(configuration C),因带 ^~ 停止正则搜索,使用 C(虽然它也匹配 \.(gif|jpg|jpeg)$,但正则不会被检查);
  • /documents/1.jpg:普通 location 最大前缀匹配到 /(configuration B),继续正则搜索,命中 ~* \.(gif|jpg|jpeg)$,正则覆盖普通结果,使用 D。

原文还提到:

Note that you could define these 4 configurations in any order and the results would remain the same.

需要提醒的是:这里说"可以按任意顺序定义且结果不变",是因为上面只有一个正则 location。前文已经强调,正则 location 的匹配与编辑顺序有关;如果存在多个正则 location,顺序就会影响结果。

6.3 关于 location 嵌套

原文:

While nested locations are allowed by the configuration file parser, their use is discouraged and may produce unexpected results.

Nginx 的配置文件解析程序允许 location 嵌套定义(如 location / { location /uri/ {} }),但平时很少见到这种配置,因为官方并不建议这样做——它可能导致很多意想不到的后果。


七、安装 Nginx

本节附带说明源码安装 Nginx 的过程(示例版本为 1.4.6)。

wget http://nginx.org/download/nginx-1.4.6.tar.gz
tar zxvf nginx-1.1.0.tar.gz
./configure
make
make install

在 ./configure 这一步可能遇到如下报错:

./configure: error: the HTTP rewrite module requires the PCRE library.

安装 Nginx 时,rewrite 模块默认会被安装,但 rewrite 模块所依赖的 PCRE 库需要额外安装。报错提示给出了三种解决思路:

You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.

即:使用 --without-http_rewrite_module 禁用该模块;或把 PCRE 库安装到系统中;或使用 --with-pcre=<path> 让 Nginx 从源码静态编译 PCRE。

通常的解决办法是先安装依赖:

yum -y install pcre-devel openssl openssl-devel

备注:PCRE(Perl Compatible Regular Expressions)是 Perl 兼容的正则表达式库。Nginx 的 rewrite 正则表达式采用的是 Perl 语法集(常识:正则表达式有不同的语法集,常见的 grep 指令若需采用 Perl 语法集,需要用 grep -P 指定)。


八、location 实例演练

Nginx 的语法形式是 location [=|~|~*|^~|@] /uri/ { ... },即可以用 =、~*、~、^~、@ 作为前缀,也可以没有前缀([A] 表示可选的 A,A|B 表示 A、B 二选一),紧接着是 /uri/,再是 {...} 指令块,整体含义是:对满足条件的 /uri/ 适用指令块中的指令。

如前所述,location 分两大类:普通 location(以 =、^~ 为前缀或不带任何前缀)和正则 location(以 ~、~* 为前缀)。

当一个 server 中编写了多个 location 时,Nginx 如何为一个 HTTP 请求选定处理的 location?核心仍然是那句话:正则 location 让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果。 下面通过实例说明。

8.1 例题 1:先普通 location,再正则 location

很多人以为 Nginx 是"先匹配正则 location 再匹配普通 location",这是误区。Nginx 实际上是"先匹配普通 location,再匹配正则 location",但普通 location 的匹配结果又分两种:一种是"严格精确匹配(exact match)",另一种是"最大前缀匹配(the most specific match)"。

假设 Nginx 配置如下:

server {
    listen       9090;
    server_name  localhost;

    location / {
        root   html;
        index  index.html index.htm;
        deny all;
    }

    location ~ \.html$ {
        allow all;
    }
}

附录:Nginx 的目录结构为 nginx/html/index.html。

配置含义:

  • location / { ... deny all; }:普通 location,以 / 开始的 URI 请求都拒绝访问(任何 HTTP 请求都必然以 / 开始,所以 / 能匹配所有请求);
  • location ~ \.html$ { allow all; }:正则 location,以 .html 结尾的 URI 请求都允许访问。

测试结果:

[root@web108 ~]# curl http://localhost:9090/
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.1.0</center>
</body>
</html>
[root@web108 ~]# curl http://localhost:9090/index.html
<html>
<head>
<title>Welcome to nginx!</title>
</head>
<body bgcolor="white" text="black">
<center><h1>Welcome to nginx!</h1></center>
</body>
</html>
[root@web108 ~]# curl http://localhost:9090/index_notfound.html
<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.1.0</center>
</body>
</html>

汇总如下:

URI 请求 HTTP 响应
curl http://localhost:9090/ 403 Forbidden
curl http://localhost:9090/index.html Welcome to nginx!
curl http://localhost:9090/index_notfound.html 404 Not Found

推演:

  • curl http://localhost:9090/ 返回 403 Forbidden,说明匹配到了 location / { ... deny all; }。原因是请求 GET / 被普通 location / 严格精确匹配,于是停止搜索正则 location;
  • curl http://localhost:9090/index.html 返回 Welcome to nginx!,说明没有被 deny all 命中(否则会 403)。虽然 /index.html 也以 / 开头,但此时普通 location / 的匹配结果是"最大前缀匹配",所以 Nginx 继续搜索正则 location;location ~ \.html$ 对以 .html 结尾的请求 allow all,于是访问到实际存在的 index.html 页面;
  • curl http://localhost:9090/index_notfound.html 同理:先匹配 location / {}(最大前缀匹配),随后被正则 location ~ \.html$ 覆盖,最终 allow all;但目录下并不存在 index_notfound.html,因此返回 404 Not Found。

如果访问 http://localhost:9090/index.txt 会是什么结果?显然是 deny all:先匹配上 location / { ... deny all; }(最大前缀匹配),继续搜索正则 location,但 /index.txt 不以 .html 结尾,正则 location 匹配失败,最终采纳普通 location 的最大前缀匹配结果。

[root@web108 ~]# curl http://localhost:9090/index.txt
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.1.0</center>
</body>
</html>

8.2 例题 2:普通 location 的"隐式"严格匹配

在例题 1 的基础上增加一条精确配置:

server {
    listen       9090;
    server_name  localhost;

    location /exact/match.html {
        allow all;
    }

    location / {
        root   html;
        index  index.html index.htm;
        deny all;
    }

    location ~ \.html$ {
        allow all;
    }
}

测试请求:

[root@web108 ~]# curl http://localhost:9090/exact/match.html
<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.1.0</center>
</body>
</html>

结果进一步验证了"普通 location 的严格精确匹配会终止对正则 location 的搜索"。

小结:先匹配普通 location,再匹配正则 location;但如果普通 location 的匹配结果恰好是"严格精确匹配(exact match)",Nginx 不再尝试后面的正则 location;如果普通 location 的匹配结果是"最大前缀匹配",则正则 location 的匹配覆盖普通 location 的匹配。即:正则 location 让步普通 location 的严格精确匹配结果,但覆盖普通 location 的最大前缀匹配结果。

8.3 例题 3:普通 location 的"显式"严格匹配与 ^~ 前缀

前面演示的普通 location 都不带任何前缀。实际上普通 location 也可以加 ^~ 和 = 前缀:

  • ^~:意为"非正则,不需要继续正则匹配"。通常普通 location 还会继续搜索正则 location(恰好严格精确匹配除外),但 ^~ 告诉 Nginx:本条普通 location 无论是最大前缀匹配还是严格精确匹配,都终止继续搜索正则 location;
  • =:表示普通 location 不接受"最大前缀"匹配结果,必须严格等于,即严格精确匹配。

例题 3:^~ 前缀的使用

server {
    listen       9090;
    server_name  localhost;

    location /exact/match.html {
        allow all;
    }

    location ^~ / {
        root   html;
        index  index.html index.htm;
        deny all;
    }

    location ~ \.html$ {
        allow all;
    }
}

把例题 2 中的 location / {} 修改为 location ^~ / {},测试结果对比如下:

URI 请求 修改前 修改后
curl http://localhost:9090/ 403 Forbidden 403 Forbidden
curl http://localhost:9090/index.html Welcome to nginx! 403 Forbidden
curl http://localhost:9090/index_notfound.html 404 Not Found 403 Forbidden
curl http://localhost:9090/exact/match.html 404 Not Found 404 Not Found

除了 GET /exact/match.html 是 404 Not Found,其余都是 403 Forbidden。原因很简单:所有请求都以 / 开头,因此都能匹配上 / 这个普通 location;但普通 location 的匹配原则是"最大前缀",所以只有 /exact/match.html 匹配到更具体的 location /exact/match.html { allow all; },其余都命中 location ^~ / { deny all; } 并终止正则搜索。

例题 4:= 前缀的使用

server {
    listen       9090;
    server_name  localhost;

    location /exact/match.html {
        allow all;
    }

    location = / {
        root   html;
        index  index.html index.htm;
        deny all;
    }

    location ~ \.html$ {
        allow all;
    }
}

例题 4 相对例题 2 把 location / {} 改成了 location = / {},测试结果对比如下:

URI 请求 修改前 修改后
curl http://localhost:9090/ 403 Forbidden 403 Forbidden
curl http://localhost:9090/index.html Welcome to nginx! Welcome to nginx!
curl http://localhost:9090/index_notfound.html 404 Not Found 404 Not Found
curl http://localhost:9090/exact/match.html 404 Not Found 404 Not Found
curl http://localhost:9090/test.jsp 403 Forbidden 404 Not Found

最能说明问题的是 GET /test.jsp:/test.jsp 既没有匹配正则 location ~ \.html$,也没有匹配 location = / {}。如果是原来的 location / {},它会"最大前缀"匹配到普通 location /,结果是 deny all(403);而改成 location = / {} 后,/test.jsp 不满足精确匹配,也不命中正则,于是没有任何 location 命中对应指令,最终返回 404。

8.4 例题 5:正则 location 与编辑顺序

"location 的指令与编辑顺序无关"这句话不全对:

  • 对普通 location:匹配规则是最大前缀匹配(与顺序无关),若恰好是严格精确匹配结果,或带有 ^~、= 前缀(= 只能严格匹配),则停止搜索正则 location;
  • 对正则 location:匹配规则是按编辑顺序逐个匹配(与顺序有关),只要匹配上就立即停止后面的搜索。

配置 3.1 与 3.2:正则顺序影响结果

配置 3.1:

server {
    listen       9090;
    server_name  localhost;

    location ~ \.html$ {
        allow all;
    }

    location ~ ^/prefix/.*\.html$ {
        deny all;
    }
}

配置 3.2:

server {
    listen       9090;
    server_name  localhost;

    location ~ ^/prefix/.*\.html$ {
        deny all;
    }

    location ~ \.html$ {
        allow all;
    }
}

测试结果:

URI 请求 配置 3.1 配置 3.2
curl http://localhost:9090/regextest.html 404 Not Found 404 Not Found
curl http://localhost:9090/prefix/regextest.html 404 Not Found 403 Forbidden

解释:

  • location ~ ^/prefix/.*\.html$ { deny all; }:对以 /prefix/ 开头、.html 结尾的所有 URI 请求拒绝访问;
  • location ~ \.html$ { allow all; }:对以 .html 结尾的 URI 请求允许访问。

实际上,^/prefix/.*\.html$ 匹配的集合是 \.html$ 的子集。

  • 在配置 3.1 下:两个请求都先匹配上 location ~ \.html$ { allow all; } 并停止后续搜索,于是都允许访问(页面不存在故 404 Not Found);
  • 在配置 3.2 下:/regextest.html 不匹配 prefix,继续搜索 ~ \.html$,允许访问(404 Not Found);而 /prefix/regextest.html 先匹配到 prefix,deny all(403 Forbidden)。

配置 3.3 与 3.4:普通 location 与顺序无关

配置 3.3:

server {
    listen       9090;
    server_name  localhost;

    location /prefix/ {
        deny all;
    }

    location /prefix/mid/ {
        allow all;
    }
}

配置 3.4:

server {
    listen       9090;
    server_name  localhost;

    location /prefix/mid/ {
        allow all;
    }

    location /prefix/ {
        deny all;
    }
}

测试结果:

URI 请求 配置 3.3 配置 3.4
curl http://localhost:9090/prefix/t.html 403 Forbidden 403 Forbidden
curl http://localhost:9090/prefix/mid/t.html 404 Not Found 404 Not Found

注:原文表头写作"配置 3.1 / 配置 3.2",但对应的数据实为配置 3.3 与 3.4 的结果,此处已按上下文订正。

测试结果表明:普通 location 的匹配规则是"最大前缀"匹配,而且与编辑顺序无关。两个配置只是交换了两条普通 location 的先后顺序,结果完全一致——/prefix/t.html 最大前缀匹配到 /prefix/(deny all,403),/prefix/mid/t.html 最大前缀匹配到更长的 /prefix/mid/(allow all,页面不存在故 404)。

8.5 例题 6:@ 前缀 Named Location 的使用

参考官方文档 http://wiki.nginx.org/HttpCoreModule#error_page。假设配置如下:

server {
    listen       9090;
    server_name  localhost;

    location / {
        root   html;
        index  index.html index.htm;
        allow all;
    }

    # error_page 404 http://www.baidu.com;   # 直接这样写是不允许的
    error_page 404 = @fallback;

    location @fallback {
        proxy_pass http://www.baidu.com;
    }
}

配置含义:如果请求的 URI 存在,本 Nginx 返回对应页面;如果不存在,则把请求代理到 baidu.com 上去做弥补(Nginx 发现 URI 对应页面不存在时,HTTP 状态码为 404,此时 error_page 404 指令能捕获它,并通过 @fallback 命名 location 完成内部转发)。

测试一:访问一个两边都不存在的页面

[root@web108 ~]# curl http://localhost:9090/nofound.html -i
HTTP/1.1 302 Found
Server: nginx/1.1.0
Date: Sat, 06 Aug 2011 08:17:21 GMT
Content-Type: text/html; charset=iso-8859-1
Location: http://localhost:9090/search/error.html
Connection: keep-alive
Cache-Control: max-age=86400
Expires: Sun, 07 Aug 2011 08:17:21 GMT
Content-Length: 222

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://www.baidu.com/search/error.html">here</a>.</p>
</body></html>

当 GET /nofound.html 发送给本 Nginx,Nginx 找不到对应页面,于是 error_page 404 = @fallback 生效,请求被代理到 http://www.baidu.com。Nginx 向百度发送了 GET /nofound.html,但该页面在百度也不存在,百度返回 302 跳转到错误页。

直接访问 http://www.baidu.com/nofound.html 的结果:

[root@web108 ~]# curl http://www.baidu.com/nofound.html -i
HTTP/1.1 302 Found
Date: Sat, 06 Aug 2011 08:20:05 GMT
Server: Apache
Location: http://www.baidu.com/search/error.html
Cache-Control: max-age=86400
Expires: Sun, 07 Aug 2011 08:20:05 GMT
Content-Length: 222
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://www.baidu.com/search/error.html">here</a>.</p>
</body></html>

测试二:访问 Nginx 不存在但百度存在的页面

[root@web108 ~]# curl http://www.baidu.com/duty/ -i
HTTP/1.1 200 OK
Date: Sat, 06 Aug 2011 08:21:56 GMT
Server: Apache
P3P: CP=" OTI DSP COR IVA OUR IND COM "
Set-Cookie: BAIDUID=5C5D2B2FD083737A0C88CA7075A6601A:FG=1; expires=Sun, 05-Aug-12 08:21:56 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1
Last-Modified: Wed, 05 Jan 2011 06:44:53 GMT
ETag: "d66-49913b8efe340"
Accept-Ranges: bytes
Content-Length: 3430
Cache-Control: max-age=86400
Expires: Sun, 07 Aug 2011 08:21:56 GMT
Vary: Accept-Encoding,User-Agent
Connection: Keep-Alive
Content-Type: text/html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
...
</body>
</html>

可见百度这个页面确实存在。再通过本地 Nginx 访问:

[root@web108 ~]# curl http://localhost:9090/duty/ -i
HTTP/1.1 200 OK
Server: nginx/1.1.0
Date: Sat, 06 Aug 2011 08:23:23 GMT
Content-Type: text/html
Connection: keep-alive
P3P: CP=" OTI DSP COR IVA OUR IND COM "
Set-Cookie: BAIDUID=8FEF0A3A2C31D277DCB4CC5F80B7F457:FG=1; expires=Sun, 05-Aug-12 08:23:23 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1
Last-Modified: Wed, 05 Jan 2011 06:44:53 GMT
ETag: "d66-49913b8efe340"
Accept-Ranges: bytes
Content-Length: 3430
Cache-Control: max-age=86400
Expires: Sun, 07 Aug 2011 08:23:23 GMT
Vary: Accept-Encoding,User-Agent

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
...
</body>
</html>

当 curl http://localhost:9090/duty/ -i 时,Nginx 没找到对应页面,于是 error_page = @fallback 把请求代理到 baidu.com。需要特别注意:这里的 error_page = @fallback 不是靠重定向实现的,而是前文所说的 "internally redirected(forward)"——服务端内部转发,浏览器地址栏不会变化。


九、总结

  1. 匹配总顺序:先匹配普通 location,再匹配正则 location。正则 location 并非"先执行",它之所以看起来优先,是因为它会覆盖普通 location 的最大前缀匹配结果。

  2. 普通 location 内部:采用"最大前缀匹配"(the most specific match),与 location 在配置文件中的编辑顺序无关。

  3. 正则 location 内部:严格按照在配置文件中的物理(编辑)顺序逐个匹配,命中第一条即停止,与编辑顺序密切相关。因此,更具体、范围更小的正则应放在前面。

  4. 终止正则搜索的三种情形:

    • 普通 location 带 = 前缀,发生严格精确匹配;
    • 普通 location 带 ^~ 前缀,命中最大前缀匹配;
    • 普通 location 不带 = 或 ^~,但其最大前缀匹配恰好就是严格精确匹配(隐式精确匹配)。
  5. 最终决策:若正则 location 有命中,使用正则结果(覆盖普通 location 的最大前缀匹配);若正则 location 无命中,回退使用普通 location 的最大前缀匹配结果。

  6. @ 命名 location:不参与普通请求匹配,仅用于 error_page、try_files 等触发的内部重定向(forward)。

  7. 一句话记忆:正则 location 让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果。

最后修改:2026 年 09 月 20 日
如果觉得我的文章对你有用,请随意赞赏