Nginx的虚拟服务器域名配置

时间:2024-03-07 10:02:25
虚拟服务器名(server name)是通过指令server_name来指定的。在《 Nginx是如何处理Request的?》一节中,我们讲到nginx分两步来匹配过来的Request请求:
1.选择server
2.选择location
在第1步中,其实也分两步:
1).匹配port
2).匹配server_name
这一节就来聊聊nginx是如何具体匹配server_name的。

server_name指令
server_name的形式有三种:
1.精确域名形式(exact names)
2.通配符(*)形式
3.正则表达式形式
如下:
server {
    listen      80;
    server_name  example.org  www.example.org;
    ...
}
 
server {
    listen      80;
    server_name  *.example.org;
    ...
}
 
server {
    listen      80;
    server_name  mail.*;
    ...
}
 
server {
    listen      80;
    server_name  ~^(?<user>.+)\.example\.net$;
    ...
}
通配符形式,其实又分为前向通配符后向通配符(分别如第二,第三个例子),通配符不能位于字符串的中间位置。
 
当然,完全存在这么一种情况,一个host同时匹配上面三种的一种或者多种形式,比如host 为www.example.com可以同时匹配;
server_name   *.example.com *.com www.example.com  www.example.*  www.*   ~^(.+)\.example\.com$  
server_name  有6个指令参数,www.example.com都是匹配的,那么最后选择哪一个呢?有一定的顺序:
  1. 精确域名匹配,www.example.com 
  2. 以通配符*开始的,最长的那个域名,*.example.com
  3. 以通配符*结束的,最长的那个域名,www.example.*
  4. 最后是正则表达式形式的,按照在配置文件中出现的顺序,依次尝试进行匹配,选取第一个被匹配到的域名,~^(.+)\.example\.com$
 
精确域名形式非常简单,接下来分别对server_name的通配符和正则表达式两种形式做个介绍。
 
通配符
通配符*在server name中的使用非常严格:只能位于域名的头部或者尾部,不能出现在中间;并且必须以"."分隔开
*.example.com
www.example.*
以下都是不合法的形式:
www.*.example.org
w*.example.org
当然,如果要达到后两者所体现的目的,可以使用正则表达式,例如,上面不合法的两个域名可以写成这样:
~^www\..+\.example\.org$
~^w.*\.example\.org$
 
另外一种特殊的形式可以同时匹配example.org和*.example.org,这就是
.example.org
 
正则表达式
nginx的正则表达式语法使用的是Perl语言(PCRE)的正则语法。基本形式为
server_name  ~^www\d+\.example\.net$;
这则表达式需要注意的几点
  • 必须以~开始,没有~符号的要么被视作完全匹配或者通配符匹配
  • ~和正则表达式主体之间没有空格
  • 正则表达式主体通常以^开始以$结束(虽说语法上不一定要求如此,但是从逻辑意义上强烈要求这么做)。
  • 正则表达式中,点号"."必须转义,写作"\.";正则表达式可以不用引号包住,但是,如果其中包含"{"和"}"则必须用双引号包裹
例如:
server_name  "~^(?<name>\w\d{1,3}+)\.example\.net$";
如果不加引号,nginx便无法正确加载配置文件,并报一个错误:
directive "server_name" is not terminated by ";" in ...
 
正则表达式使用命名捕获组,例如:
server {
    server_name ~^(?<myname>.+)\.example\.cn$;
    root /var/www/hb/$myname;   
}
 
PCRE语法支持下面几种捕获语法:
?<name>
?\'name\'
?P<name>
前面两者是最新的语法,第三种是老的写法。如果nginx报下面错误:
pcre_compile() failed: unrecognized character after (?< in ...
说明,你应该将?<name>或者?\'name\'改为较古老的?P<name>写法了。
 
同样,使用普通捕获组也是可以的:
server {
    server_name ~^(.+)\.example\.cn$;
    root /var/www/hb/$1;  
}
当然,普通捕获组要慎用,因为很容易被后面的正则所覆盖。
 
其他形式
除了两面提到的几种形式,sername_name的指令参数还有可能是其他的几种形式。
如果请求Request没有Host的头部,那么如果想要匹配,可以用空字符串:
server {
    listen      80;
    server_name  example.org  www.example.org  "";
    ...
}
 
另外,如果在server上下文中,没有定义 server_name,那么nginx使用空字符串作为虚拟机名称。
如果使用IP而不是域名来发起请求,那么Host请求头就是一个IP,此时server_name也可以写成一个IP:
server {
    listen      80;
    server_name  example.org
                www.example.org
                ""
                192.168.1.1
                ;
    ...
}
 
"_"可以用来匹配所有的域名
server {
    listen      80  default_server;
    server_name  _;
    return      444;
}
其他的字符,"-"和"!@#"也是可以的。注意,匹配所有域名的不能是"*"
 
最佳实践
我们知道nginx是一个款高性能的web服务器,其设计充满了许多优化的技巧。在使用的时候也不例外,如果我们能对nginx的设计原理有一些了解,我们在配置时就能很好的利用这些设计,从而使得nginx的效率达到最大化。
前面提到,server_name的指令参数匹配有一定的匹配顺序,即最先匹配精确域名形式,然后匹配以通配符*开始的域名,其次匹配以通配符*结束的域名,最后是匹配正则形式。如果前面匹配到了,就会终止继续匹配。
从原理上说,这是因为,nginx会为每个监听的port分别维护精确域名,前向通配符和后项通配符的Hash表。Hash表能在nginx启动的配置阶段得到创建和优化。精确域名的Hash表首先被搜寻,如果找不到,前向通配Hash表会被接着被搜寻,如果也没有找到,那么后向通配Hash表会被搜寻。搜寻通配Hash表要比精确域名Hash表要慢,因为其是按照域名的部分来做搜寻的(比如,*.example.com,会搜寻example和com部分)。
值得注意的是:".example.org"被存在通配Hash表里面,并没有存在精确Hash表里面,因此匹配它是较慢的。
如果以上两种方式都还没有匹配上,那么最后轮到正则形式的指令上场了。正则形式的域名是按照先后顺序一个一个的去匹配的,没有存入任何Hash表,匹配到正确的就结束,因此,这是最慢的形式,没有任何“技巧”可言。
因此,最好的配置方式就是,尽可能使用精确域名,其次是通配符形式的,最后是正则形式。即便是正则形式域名,也要根据实际需要将用的最多的域名尽量前置。这样方可使得nginx的性能达到最大化。
例如:
server {
    listen      80;
    server_name  example.org  www.example.org  *.example.org;
    ...
}
这种方式要优于:
server {
    listen      80;
    server_name  .example.org;
    ...
}
 
长域名,多域名的情况
在某些情况下,域名会非常的长,nginx不会允许其无限长,默认最大为32。在http上下文中,你可以通过server_names_hash_bucket_size指令来设置,可选参数有32,64(2的N次方)等
例如,如果域名被定义为:"too.long.server.name.example.org",超过32字符,那么会报错:
could not build the server_names_hash,
you should increase server_names_hash_bucket_size: 32
解决方式:
http {
    server_names_hash_bucket_size  64;
    ...
 
在另一些情况下,server_name配置的域名又很多,nginx同样可能报错:
could not build the server_names_hash,
you should increase either server_names_hash_max_size: 512
or server_names_hash_bucket_size: 32
这种情况下,先设置server_names_hash_max_size为一个接近你域名总数的一个合理值,如果这个还不管用,那么再调大server_names_hash_bucket_size的值(例如将2^N调整到2^(N+1))
http {
    server_names_hash_max_size:600
    server_names_hash_bucket_size 32;
    ...

如果一个域名是某个监听端口下的唯一域名,那么nginx就不会建立Hash匹配表,也不会有上面介绍的那些匹配流程,然而,如果这个唯一的域名是一个捕获组正则表达式,那么nginx还是去尝试去解析正则表达式以提取这个字段。