理解Nginx的server匹配规则
Nginx的块配置
Nginx在逻辑上将提供不同内容的配置划分为块,这些块以层次结构的形式存在(http->server->location)。客户端发出请求时,Nginx收到之后,会有一个确定应该使用哪些配置块来处理请求的过程。本文主要介绍 server 块背后的处理过程。
server块是Nginx配置的子集,它定义用于处理已定义类型请求的虚拟服务器(虚拟机)。管理员通常会配置多个server块,并根据请求的域名,端口和IP地址决定哪个块应该处理哪个连接。
Nginx如何决定哪个server块来处理请求
由于Nginx允许管理员定义多个server块作为单独的虚拟Web服务器实例,因此需要一个算法来确定将使用哪些server块来匹配请求。
Nginx在此过程中关注的主要server块指令是listen
指令和server_name
指令。
解析“listen”指令以找到可能的匹配
首先,Nginx查看请求的IP地址和端口,并与每个服务器的 listen
指令相匹配,构建可能解析请求的服务器块列表。
listen
指令通常定义 server 块将响应的IP地址和端口。默认情况下,任何不包含listen
指令的 server 块默认 listen 在0.0.0.0:80
(或者0.0.0.0:8080
如果Nginx由普通的非root用户运行),这样的配置块响应80端口上任何接口的请求,但是这个默认值在server选择过程中没有太大的权重。
该listen
指令可以设置为:
- IP地址/端口组合。
- 只有IP地址,它将监听默认端口80。
- 只有端口,它将监听该端口上的每个接口。
- Unix套接字的路径。
最后的选项通常在不同的服务器之间传递请求时起到作用。
在尝试确定向哪个服务器块发送请求时,Nginx将首先尝试listen
使用以下规则根据指令的特异性来决定:
- Nginx用默认的缺省值来替换所有不完整的lesten指令(完整:IP+port的组合)的缺省值,因此每一个server块的
listen
指令都可以看作是IP地址和端口的组合。 这种转换的例子有:- 没有
listen
指令的块使用该值0.0.0.0:80
。 - 设置为
111.111.111.111
没有端口的IP地址的块变为111.111.111.111:80
- 设置为
8888
没有IP地址的端口的块变为0.0.0.0:8888
- 没有
- 接下来Nginx会尝试去收集一个server块的列表,这个列表是基于具体的IP和端口最佳匹配。也就是说如果匹配的server块有具体的IP地址,它就不会匹配用0.0.0.0作为默认的IP地址的server块。无论什么情况,在Nginx选择server块的过程中,端口必须准确匹配。
- 如果只有一个最具体的匹配,那么该server块将用于提供请求。如果有多个server 块具有相同层次的具体匹配,那么Nginx需继续评估
server_name
指令 。
需要特别注意的是,只有 listen 指令在同一层次上有多个匹配的 server 块时,Nginx才会继续评估server_name
指令。举个例子,如果域名example.com被解析到IP为192.168.1.10,端口为80的主机上,当客户端请求example.com时,在本例中,第一个server模块总是会提供服务,尽管server_name
指令在第二个server模块中。
1 2 3 4 | server{ listen 192.168.1.10; .... } |
1 2 3 4 5 | server{ listen 80; server_name example.com; .... } |
多个server模块在具体的匹配中处于同一级别的情况下,Nginx下一步才会检查server_name
指令。
解析server_name指令选择一个匹配
接下来,为了进一步评估具有相同特定listen
指令的请求,Nginx会检查请求的“host”标头,此值包含客户端实际尝试访问的域或IP地址。
Nginx在候选的每一个server模块中,查看其server_name
指令,尝试去找到最佳的匹配。Nginx通过下面的公式来进行评估:
- Nginx首先找到
server_name
与请求的Host头信息精准匹配的server模块,如果找到了这个server模块,它将会被用于服务客户端的请求。若有多个特定的匹配项被找到,第一个会被用于提供服务。 - 如果没有找到精准的匹配项,Nginx接下来将尝试去找
server_name
与前置通配符(在配置中名称的开头用*表示)匹配的server模块。只要找到一个,这个server模块将被用于为客户端提供服务。如果找到了多个匹配,最长匹配结果的server模块将会被用于提供服务。 - 如果使用前置通配符没有找到匹配时,Nginx接下来将尝试去找
server_name
与后置通配符(在配置中名称的结尾用*表示)匹配的server模块。只要找到一个,这个server模块将被用于为客户端提供服务。如果找到了多个匹配,最长匹配结果的server模块将会被用于提供服务。 - 如果使用后置通配符没有找到匹配时,Nginx接下来将会评估用正则表达式(在名称前用~表示)定义
server_name
的server模块。带有与Host头匹配的正则表达式的第一个server_name
将被用于提供服务。 - 如果没有找到用正则表达式定义
server_name
的相匹配的server模块时,Nginx接下来会使用默认IP和端口的server模块。
每一个IP地址/端口组合都有一个默认的server模块,当用上面的方法不能确定一个操作的过程时将使用默认的server模块。对于IP地址/端口的组合来说,这将是配置中的第一个模块或者是包含default_server
选项作为listen
指令的一部分的server模块(这将复写first-found算法)。每一个IP地址/端口组合只能有一个default_server
声明。
实例
如果已定义的server_name
与Host头的值精准匹配时,这个server模块将被选择来处理请求。
在这个例子中,如果请求的Host头的值被设置为 host1.example.com,第二个server模块将被选中:
1 2 3 4 5 | server{ listen 80; server_name *.example.com; ... } |
1 2 3 4 5 | server{ listen 80; server_name host1.example.com; ... } |
如果精准的匹配没有被找到时,Nginx将会检查是否有一个具有适合前置通配符的server_name
。以通配符开始的最长的server_name
的server模块将会被选择来完成响应。
在这个例子中,如果请求的Host头是 www.example.org,第二个server模块将被选中:
1 2 3 4 5 | server{ listen 80; server_name www.example.*; ... } |
1 2 3 4 5 | server{ listen 80; server_name *.example.org; ... } |
1 2 3 4 | server{ listen 80; server_name *.org; } |
若server_name
以通配符开始的模块没有找到,Nginx将查看在表达式后面有通配符的匹配项是否存在。此时,以通配符结尾的最长的匹配项将被用于服务客户端的请求。
在这个例子中,如果请求的Host头被设置为 www.example.com,第三个模块将被选中:
1 2 3 4 5 | server{ listen 80; server_name host1.example.com; ... } |
1 2 3 4 | server{ listen 80; server_name example.com; } |
1 2 3 4 | server{ listen 80; server_name www.example.*; } |
如果通配符匹配项没有找到,Nginx将会去匹配用了正则表达式的server_name
。第一个匹配上的server模块将会被选中来响应请求。
在这个例子中,如果请求的Host头设置为 www.example.com,那么第二个server模块将被选中来完成响应。
1 2 3 4 5 | server{ listen 80; server_name example.com; ... } |
1 2 3 4 5 | server{ listen 80; server_name ~^(www|host1).*\.example\.com$; ... } |
1 2 3 4 5 | server{ listen 80; server_name ~^(subdomain|set|www|host1).*\.example\.com$; ... } |
如果上述步骤都不能满足请求,则该请求将被传递到默认的server模块以获取匹配的IP地址和端口。