Nginx:配置指南

Nginx如何处理一个请求

基于名字的虚拟主机

  Nginx首先选定由哪一个虚拟主机来处理请求。让我们从一个简单的配置(其中全部3个虚拟主机都在端口*:80上监听)开始:

server {
    listen      80;
    server_name example.org www.example.org;
    ...
}

server {
    listen      80;
    server_name example.net www.example.net;
    ...
}

server {
    listen      80;
    server_name example.com www.example.com;
    ...
}

  在这个配置中,nginx仅仅检查请求的“Host”头以决定该请求应由哪个虚拟主机来处理。如果Host头没有匹配任意一个虚拟主机,或者请求中根本没有包含Host头,那nginx会将请求分发到定义在此端口上的默认虚拟主机。在以上配置中,第一个被列出的虚拟主机即nginx的默认虚拟主机——这是nginx的默认行为。而且,可以显式地设置某个主机为默认虚拟主机,即在"listen"指令中设置"default_server"参数:

server {
    listen      80 default_server;
    server_name example.net www.example.net;
    ...
}
" default_server"参数从0.8.21版开始可用。在之前的版本中,应该使用" default"参数代替。

  请注意"default_server"是监听端口的属性,而不是主机名的属性。后面会对此有更多介绍。


如何防止处理未定义主机名的请求

  如果不允许请求中缺少“Host”头,可以定义如下主机,丢弃这些请求:

server {
    listen       80;
    server_name  "";
    return       444;
}

  在这里,我们设置主机名为空字符串以匹配未定义“Host”头的请求,而且返回了一个nginx特有的,非http标准的返回码444,它可以用来关闭连接。

从0.8.48版本开始,这已成为主机名的默认设置,所以可以省略server_name ""。而之前的版本使用机器的hostname作为主机名的默认值。

基于域名和IP混合的虚拟主机

  下面让我们来看一个复杂点的配置,在这个配置里,有几个虚拟主机在不同的地址上监听:

server {
    listen      192.168.1.1:80;
    server_name example.org www.example.org;
    ...
}

server {
    listen      192.168.1.1:80;
    server_name example.net www.example.net;
    ...
}

server {
    listen      192.168.1.2:80;
    server_name example.com www.example.com;
    ...
}

  这个配置中,nginx首先测试请求的IP地址和端口是否匹配某个server配置块中的listen指令配置。接着nginx继续测试请求的Host头是否匹配这个server块中的某个server_name的值。如果主机名没有找到,nginx将把这个请求交给默认虚拟主机处理。例如,一个从192.168.1.1:80端口收到的访问www.example.com的请求将被监听192.168.1.1:80端口的默认虚拟主机处理,本例中就是第一个服务器,因为这个端口上没有定义名为www.example.com的虚拟主机。

  默认服务器是监听端口的属性,所以不同的监听端口可以设置不同的默认服务器:

server {
    listen      192.168.1.1:80;
    server_name example.org www.example.org;
    ...
}

server {
    listen      192.168.1.1:80 default_server;
    server_name example.net www.example.net;
    ...
}

server {
    listen      192.168.1.2:80 default_server;
    server_name example.com www.example.com;
    ...
}

一个简单PHP站点配置

  现在我们来看在一个典型的,简单的PHP站点中,nginx怎样为一个请求选择location来处理:

server {
    listen      80;
    server_name example.org www.example.org;
    root        /data/www;

    location / {
        index   index.html index.php;
    }

    location ~* \.(gif|jpg|png)$ {
        expires 30d;
    }

    location ~ \.php$ {
        fastcgi_pass  localhost:9000;
        fastcgi_param SCRIPT_FILENAME
                      $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}

  首先,nginx使用前缀匹配找出最准确的location,这一步nginx会忽略location在配置文件出现的顺序。上面的配置中,唯一的前缀匹配location是"/",而且因为它可以匹配任意的请求,所以被作为最后一个选择。接着,nginx继续按照配置中的顺序依次匹配正则表达式的location,匹配到第一个正则表达式后停止搜索。匹配到的location将被使用。如果没有匹配到正则表达式的location,则使用刚刚找到的最准确的前缀匹配的location。

  请注意所有location匹配测试只使用请求的URI部分,而不使用参数部分。这是因为写参数的方法很多,比如:

/index.php?user=john&page=1
/index.php?page=1&user=john

  除此以外,任何人在请求串中都可以随意添加字符串:

/index.php?page=1&something+else&user=john

  现在让我们来看使用上面的配置,请求是怎样被处理的:

  • 请求"/logo.gif"首先匹配上location "/",然后匹配上正则表达式"\.(gif|jpg|png)$"。因此,它将被后者处理。根据"root /data/www"指令,nginx将请求映射到文件/data/www/logo.gif",并发送这个文件到客户端。
  • 请求"/index.php"首先也匹配上location "/",然后匹配上正则表达式"\.(php)$"。 因此,它将被后者处理,进而被发送到监听在localhost:9000的FastCGI服务器。fastcgi_param指令将FastCGI的参数SCRIPT_FILENAME的值设置为"/data/www/index.php",接着FastCGI服务器执行这个文件。变量$document_root等于root指令设置的值,变量$fastcgi_script_name的值是请求的uri,"/index.php"。
  • 请求"/about.html"仅能匹配上location "/",因此,它将使用此location进行处理。根据"root /data/www"指令,nginx将请求映射到文件"/data/www/about.html",并发送这个文件到客户端。
  • 请求"/"的处理更为复杂。它仅能匹配上location "/",因此,它将使用此location进行处理。然后,index指令使用它的参数和"root /data/www"指令所组成的文件路径来检测对应的文件是否存在。如果文件/data/www/index.html不存在,而/data/www/index.php存在,此指令将执行一次内部重定向到"/index.php",接着nginx将重新寻找匹配"/index.php"的location,就好像这次请求是从客户端发过来一样。正如我们之前看到的那样,这个重定向的请求最终交给FastCGI服务器来处理。

虚拟主机名

  虚拟主机名使用server_name指令定义,用于决定由某台虚拟主机来处理请求。具体请参考《nginx如何处理一个请求》。虚拟主机名可以使用确切的名字,通配符,或者是正则表达式来定义:

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$;
    ...
}

  nginx以名字查找虚拟主机时,如果名字可以匹配多于一个主机名定义,比如同时匹配了通配符的名字和正则表达式的名字,那么nginx按照下面的优先级别进行查找,并选中第一个匹配的虚拟主机:

  1. 确切的名字;
  2. 最长的以星号起始的通配符名字:*.example.org
  3. 最长的以星号结束的通配符名字:mail.*
  4. 第一个匹配的正则表达式名字(按在配置文件中出现的顺序)。


通配符名字

  通配符名字只可以在名字的起始处或结尾处包含一个星号,并且星号与其他字符之间用点分隔。所以,“www.*.example.org”和“w*.example.org”都是非法的。不过,上面的两个名字可以使用正则表达式描述,即“~^www\..+\.example\.org$”和“~^w.*\.example\.org$”。星号可以匹配名字的多个节(各节都是以点号分隔的)。“*.example.org”不仅匹配www.example.org,也匹配www.sub.example.org

  有一种形如“.example.org”的特殊通配符,它可以既匹配确切的名字“example.org”,又可以匹配一般的通配符名字“*.example.org”。


正则表达式名字

  nginx使用的正则表达式兼容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   ~^(www\.)?(?<domain>.+)$;

    location / {
        root   /sites/$domain;
    }
}

  PCRE使用下面语法支持命名捕获组:

?<name>从PCRE-7.0开始支持,兼容Perl 5.10语法
?'name'从PCRE-7.0开始支持,兼容Perl 5.10语法
?P<name>从PCRE-4.0开始支持,兼容Python语法
  如果nginx不能启动,并显示错误信息:
pcre_compile() failed: unrecognized character after (?< in ...

  说明PCRE版本太旧,应该尝试使用?P<name>。捕获组也可以以数字方式引用:

server {
    server_name   ~^(www\.)?(.+)$;

    location / {
        root   /sites/$2;
    }
}

  不过,这种用法只限于简单的情况(比如上面的例子),因为数字引用很容易被覆盖。


其他类型的名字

  有一些主机名会被特别对待。

  如果需要用一个非默认的虚拟主机处理请求头中不含“Host”字段的请求,需要指定一个空名字:

server {
    listen       80;
    server_name  example.org  www.example.org  "";
    ...
}

  如果server块中没有定义server_name,nginx使用空名字作为虚拟主机名。

nginx 0.8.48版本以下(含)在同样的情况下会使用机器名作为虚拟主机名。

  如果以“$hostname”(nginx 0.9.4及以上版本)定义虚拟主机名,机器名将被使用。

  如果使用IP地址而不是主机名来请求服务器,那么请求头的“Host”字段包含的将是IP地址。可以将IP地址作为虚拟主机名来处理这种请求:

server {
    listen       80;
    server_name  nginx.org
                 www.nginx.org
                 ""
                 192.168.1.1
                 ;
    ...
}

  在匹配所有的服务器的例子中,可以见到一个奇怪的名字“_”:

server {
    listen       80  default_server;
    server_name  _;
    return       444;
}

  这没什么特别的,它只不过是成千上万的与真实的名字绝无冲突的非法域名中的一个而已。当然,也可以使用“--”和“!@#”等等。

  nginx直到0.6.25版本还支持一个特殊的名字“*”,这个名字一直被错误地理解成是一个匹配所有的名字。但它从来没有像匹配所有的名字,或者通配符那样工作过,而是用来支持一种功能,此功能现在已经改由server_name_in_redirect指令提供支持了。所以,现在这个特殊的名字“*”已经过时了,应该使用server_name_in_redirect指令取代它。需要注意的是,使用server_name指令无法描述匹配所有的名字或者默认服务器。这是listen指令的属性,而不是server_name指令的属性。具体请参考《nginx如何处理一个请求》。可以定义两个服务器都监听*:80和*:8080端口,然后指定一个作为端口*:8080的默认服务器,另一个作为端口*:80的默认服务器:

server {
    listen       80;
    listen       8080  default_server;
    server_name  example.net;
    ...
}

server {
    listen       80  default_server;
    listen       8080;
    server_name  example.org;
    ...
}


优化

  确切名字和通配符名字存储在哈希表中。哈希表和监听端口关联。哈希表的尺寸在配置阶段进行了优化,可以以最小的CPU缓存命中失败来找到名字。设置哈希表的细节参见这篇文档

  nginx首先搜索确切名字的哈希表,如果没有找到,搜索以星号起始的通配符名字的哈希表,如果还是没有找到,继续搜索以星号结束的通配符名字的哈希表。

  因为名字是按照域名的节来搜索的,所以搜索通配符名字的哈希表比搜索确切名字的哈希表慢。注意特殊的通配符名字“.example.org”存储在通配符名字的哈希表中,而不在确切名字的哈希表中。

  正则表达式是一个一个串行的测试,所以是最慢的,而且不可扩展。

  鉴于以上原因,请尽可能使用确切的名字。举个例子,如果使用example.orgwww.example.org来访问服务器是最频繁的,那么将它们明确的定义出来就更为有效:

server {
    listen       80;
    server_name  example.org  www.example.org  *.example.org;
    ...
}

  下面这种方法相比更简单,但是效率也更低:

server {
    listen       80;
    server_name  .example.org;
    ...
}

  如果定义了大量名字,或者定义了非常长的名字,那可能需要在http配置块中使用server_names_hash_max_sizeserver_names_hash_bucket_size指令进行调整。server_names_hash_bucket_size的默认值可能是32,或者是64,或者是其他值,取决于CPU的缓存行的长度。如果这个值是32,那么定义“too.long.server.name.example.org”作为虚拟主机名就会失败,而nginx显示下面错误信息:

could not build the server_names_hash,
you should increase server_names_hash_bucket_size: 32

  出现了这种情况,那就需要将指令的值扩大一倍:

http {
    server_names_hash_bucket_size  64;
    ...

  如果定义了大量名字,得到了另外一个错误:

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的值。

  如果只为一个监听端口配置了唯一的主机,那么nginx就完全不会测试虚拟主机名了(也不会为监听端口建立哈希表)。不过,有一个例外,如果定义的虚拟主机名是一个含有捕获组的正则表达式,这时nginx就不得不执行这个表达式以得到捕获组。


兼容性
  • 从0.9.4版本开始,支持特殊的虚拟主机名“$hostname”。
  • 从0.8.48版本开始,默认的虚拟主机名是空名字“”。
  • 从0.8.25版本开始,支持虚拟主机名中使用命名的正则表达式捕获组。
  • 从0.7.40版本开始,支持虚拟主机名中使用正则表达式的捕获组。
  • 从0.7.12版本开始,支持空名字“”。
  • 从0.6.25版本开始,通配符和正则表达式名字可以作为第一个虚拟主机名。
  • 从0.6.7版本开始,支持正则表达式的虚拟主机名。
  • 从0.6.0版本开始,支持形如example.*的通配符名字。
  • 从0.3.18版本开始,支持形如.example.org的特殊通配符名字。
  • 从0.1.13版本开始,支持形如*.example.org的通配符名字。


配置HTTPS服务器

  配置HTTPS主机,必须在server配置块中打开SSL协议,还需要指定服务器端证书和密钥文件的位置:

server {
    listen              443;
    server_name         www.example.com;
    ssl                 on;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;
    ssl_protocols       SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ...
}

  服务器证书是公开的,会被传送到每一个连接到服务器的客户端。而私钥不是公开的,需要存放在访问受限的文件中,当然,nginx主进程必须有读取密钥的权限。私钥和证书可以存放在同一个文件中:

    ssl_certificate     www.example.com.cert;
    ssl_certificate_key www.example.com.cert;

  这种情况下,证书文件同样得设置访问限制。当然,虽然证书和密钥存放在同一个文件,只有证书会发送给客户端,密钥不会发送。

  ssl_protocolsssl_ciphers指令可以用来强制用户连接只能引入SSL/TLS那些强壮的协议版本和强大的加密算法。从1.0.5版本开始,nginx默认使用“ssl_protocols SSLv3 TLSv1”和“ssl_ciphers HIGH:!aNULL:!MD5”,所以只有在之前的版本,明确地配置它们才是有意义的。从1.1.13和1.0.12版本开始,nginx默认使用“ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2”。

  CBC模式的加密算法容易受到一些攻击,尤其是BEAST攻击(参见CVE-2011-3389)。可以通过下面配置调整为优先使用RC4-SHA加密算法:

    ssl_ciphers RC4:HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

HTTPS服务器优化

  SSL操作需要消耗CPU资源,所以在多处理器的系统,需要启动多个工作进程,而且数量需要不少于可用CPU的个数。最消耗CPU资源的SSL操作是SSL握手,有两种方法可以将每个客户端的握手操作数量降到最低:第一种是保持客户端长连接,在一个SSL连接发送多个请求,第二种是在并发的连接或者后续的连接中重用SSL会话参数,这样可以避免SSL握手的操作。会话缓存用于保存SSL会话,这些缓存在工作进程间共享,可以使用ssl_session_cache指令进行配置。1M缓存可以存放大约4000个会话。默认的缓存超时是5分钟,可以使用ssl_session_timeout加大它。下面是一个针对4核系统的配置优化的例子,使用10M的共享会话缓存:

worker_processes  4;

http {
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout  10m;

    server {
        listen              443;
        server_name         www.example.com;
        keepalive_timeout   70;

        ssl                 on;
        ssl_certificate     www.example.com.crt;
        ssl_certificate_key www.example.com.key;
        ssl_protocols       SSLv3 TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         HIGH:!aNULL:!MD5;
        ...

SSL证书链

  有些浏览器不接受那些众所周知的证书认证机构签署的证书,而另外一些浏览器却接受它们。这是由于证书签发使用了一些中间认证机构,这些中间机构被众所周知的证书认证机构授权代为签发证书,但是它们自己却不被广泛认知,所以有些客户端不予识别。针对这种情况,证书认证机构提供一个证书链的包裹,用来声明众所周知的认证机构和自己的关系,需要将这个证书链包裹与服务器证书合并成一个文件。在这个文件里,服务器证书需要出现在认证方证书链的前面:

$ cat www.example.com.crt bundle.crt > www.example.com.chained.crt

  这个文件需要使用ssl_certificate指令来引用:

server {
    listen              443;
    server_name         www.example.com;
    ssl                 on;
    ssl_certificate     www.example.com.chained.crt;
    ssl_certificate_key www.example.com.key;
    ...
}

  如果服务器证书和认证方证书链合并时顺序弄错了,nginx就不能正常启动,而且会显示下面的错误信息:

SSL_CTX_use_PrivateKey_file(" ... /www.example.com.key") failed
   (SSL: error:0B080074:x509 certificate routines:
    X509_check_private_key:key values mismatch)

  因为nginx首先需要用私钥去解密服务器证书,而遇到的却是认证方的证书。

  浏览器通常会将那些被受信的认证机构认证的中间认证机构保存下来,那么这些浏览器以后在遇到使用这些中间认证机构但不包含证书链的情况时,因为已经保存了这些中间认证机构的信息,所以不会报错。可以使用openssl命令行工具来确认服务器发送了完整的证书链:

$ openssl s_client -connect www.godaddy.com:443
...
Certificate chain
 0 s:/C=US/ST=Arizona/L=Scottsdale/1.3.6.1.4.1.311.60.2.1.3=US
     /1.3.6.1.4.1.311.60.2.1.2=AZ/O=GoDaddy.com, Inc
     /OU=MIS Department/CN=www.GoDaddy.com
     /serialNumber=0796928-7/2.5.4.15=V1.0, Clause 5.(b)
   i:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
     /OU=http://certificates.godaddy.com/repository
     /CN=Go Daddy Secure Certification Authority
     /serialNumber=07969287
 1 s:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
     /OU=http://certificates.godaddy.com/repository
     /CN=Go Daddy Secure Certification Authority
     /serialNumber=07969287
   i:/C=US/O=The Go Daddy Group, Inc.
     /OU=Go Daddy Class 2 Certification Authority
 2 s:/C=US/O=The Go Daddy Group, Inc.
     /OU=Go Daddy Class 2 Certification Authority
   i:/L=ValiCert Validation Network/O=ValiCert, Inc.
     /OU=ValiCert Class 2 Policy Validation Authority
     /CN=http://www.valicert.com//emailAddress=info@valicert.com
...

  在这个例子中,www.GoDaddy.com的服务器证书(#0)的受签者(“s”)是被签发机构(“i”)签名的,而这个签发机构又是证书(#1)的受签者,接着证书(#1)的签发机构又是证书(#2)的受签者,最后证书(#2)是被众所周知的签发机构ValiCert, Inc签发。ValiCert, Inc的证书内嵌在浏览器中,被浏览器自动识别(这段话神似英国诗《在Jack盖的房子里》里面的内容)。

  如果没有加入认证方证书链,就只会显示服务器证书(#0)。


合并HTTP/HTTPS主机

  如果HTTP和HTTPS虚拟主机的功能是一致的,可以配置一个虚拟主机,既处理HTTP请求,又处理HTTPS请求。配置的方法是删除ssl on的指令,并在*:443端口添加参数ssl

server {
    listen              80;
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;
    ...
}
在0.8.21版本以前,只有添加了 default参数的监听端口才能添加 ssl参数:
listen  443  default ssl;

基于名字的HTTPS主机

  如果在同一个IP上配置多个HTTPS主机,会出现一个很普遍的问题:

server {
    listen          443;
    server_name     www.example.com;
    ssl             on;
    ssl_certificate www.example.com.crt;
    ...
}

server {
    listen          443;
    server_name     www.example.org;
    ssl             on;
    ssl_certificate www.example.org.crt;
    ...
}

  使用上面的配置,不论浏览器请求哪个主机,都只会收到默认主机www.example.com的证书。这是由SSL协议本身的行为引起的——先建立SSL连接,再发送HTTP请求,所以nginx建立SSL连接时不知道所请求主机的名字,因此,它只会返回默认主机的证书。

  最古老的也是最稳定的解决方法就是每个HTTPS主机使用不同的IP地址:

server {
    listen          192.168.1.1:443;
    server_name     www.example.com;
    ssl             on;
    ssl_certificate www.example.com.crt;
    ...
}

server {
    listen          192.168.1.2:443;
    server_name     www.example.org;
    ssl             on;
    ssl_certificate www.example.org.crt;
    ...
}

带有多个主机名的SSL证书

  也有其他一些方法可以实现多个HTTPS主机共享一个IP地址,但都有不足。其中一种方法是使用在“SubjectAltName”字段中存放多个名字的证书,比如www.example.comwww.example.org。但是,“SubjectAltName”字段的长度有限制。

  另一种方式是使用主机名中含有通配符的证书,比如*.example.org。这个证书匹配www.example.org,但是不匹配example.orgwww.sub.example.org。这两种方法可以结合在一起——使用在“SubjectAltName”字段中存放的多个名字的证书,这些名字既可以是确切的名字,也可以是通配符,比如example.org*.example.org

  最好将带有多个名字的证书和它的密钥文件配置在http配置块中,这样可以只保存一份内容拷贝,所有主机的配置都从中继承:

ssl_certificate      common.crt;
ssl_certificate_key  common.key;

server {
    listen          443;
    server_name     www.example.com;
    ssl             on;
    ...
}

server {
    listen          443;
    server_name     www.example.org;
    ssl             on;
    ...
}

主机名指示

  在一个IP上运行多个HTTPS主机的更通用的方案是TLS主机名指示扩展(SNI,RFC6066),它允许浏览器和服务器进行SSL握手时,将请求的主机名传递给服务器,因此服务器可以知道使用哪一个证书来服务这个连接。但SNI只得到有限的浏览器的支持。下面列举支持SNI的浏览器最低版本和平台信息:

  • Opera 8.0;
  • MSIE 7.0(仅在Windows Vista操作系统及后续操作系统);
  • Firefox 2.0和使用Mozilla平台1.8.1版本的其他浏览器;
  • Safari 3.2.1(Windows版需要最低Vista操作系统);
  • Chrome(Windows版需要最低Vista操作系统)。
通过SNI只能传递域名,但是,当请求中包含可读的IP地址时,某些浏览器将服务器的IP地址作为服务器的名字进行了传送。这是一个错误,大家不应该依赖于这个。

  为了在nginx中使用SNI,那么无论是在编译nginx时使用的OpenSSL类库,还是在运行nginx时使用的OpenSSL运行库,都必须支持SNI。从0.9.8f版本开始,OpenSSL通过 “--enable-tlsext” 配置选项加入SNI支持,从0.9.8j版本开始,此选项成为默认选项。当nginx被编译成支持SNI时,在使用“-V”选项运行时会显示如下信息:

$ nginx -V
...
TLS SNI support enabled
...

  但是,当开启SNI支持的nginx被动态链接到不支持SNI的OpenSSL库上时,nginx会显示如下警告:

nginx was built with SNI support, however, now it is linked
dynamically to an OpenSSL library which has no tlsext support,
therefore SNI is not available

兼容性
  • 从0.8.21和0.7.62版本开始,使用“-V”选项运行nginx时,将显示SNI支持状态信息。
  • 从0.7.14版本开始,listen指令支持ssl参数。
  • 从0.5.32版本开始,支持SNI。
  • 从0.5.6版本开始,支持SSL会话缓存,并可在工作进程间共享。

  • 0.7.65、0.8.19及以后版本,默认SSL协议是SSLv3、TLSv1、TLSc1.1和TLSv1.2(如果OpenSSL库支持)。
  • 0.7.64、0.8.18及以前版本,默认SSL协议是SSLv2、SSLv3和TLSv1。

  • 1.0.5及以后版本,默认SSL密码算法是HIGH:!aNULL:!MD5
  • 0.7.65、0.8.20及以后版本,默认SSL密码算法是HIGH:!ADH:!MD5
  • 0.8.19版本,默认SSL密码算法是ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM
  • 0.7.64、0.8.18及以前版本,默认SSL密码算法是ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP


Using nginx as HTTP load balancer

Introduction

    Load balancing across multiple application instances is a commonly used technique for optimizing resource utilization, maximizing throughput, reducing latency, and ensuring fault-tolerant configurations.

    It is possible to use nginx as a very efficient HTTP load balancer to distribute traffic to several application servers and to improve performance, scalability and reliability of web applications with nginx.


Load balancing methods

    The following load balancing mechanisms (or methods) are supported innginx:

  • round-robin — requests to the application servers are distributed in a round-robin fashion,
  • least-connected — next request is assigned to the server with the least number of active connections,
  • ip-hash — a hash-function is used to determine what server should be selected for the next request (based on the client’s IP address).

Default load balancing configuration

    The simplest configuration for load balancing with nginx may look like the following:

http {
    upstream myapp1 {
        server srv1.example.com;
        server srv2.example.com;
        server srv3.example.com;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://myapp1;
        }
    }
}

    In the example above, there are 3 instances of the same application running on srv1-srv3. When the load balancing method is not specifically configured, it defaults to round-robin. All requests are proxied to the server group myapp1, and nginx applies HTTP load balancing to distribute the requests.

    Reverse proxy implementation in nginx includes load balancing for HTTP, HTTPS, FastCGI, uwsgi, SCGI, and memcached.

    To configure load balancing for HTTPS instead of HTTP, just use “https”as the protocol.

    When setting up load balancing for FastCGI, uwsgi, SCGI, or memcached, usefastcgi_pass,uwsgi_passscgi_pass, andmemcached_pass directives respectively.


Least connected load balancing

    Another load balancing discipline is least-connected. Least-connected allows controlling the load on application instances more fairly in a situation when some of the requests take longer to complete.

    With the least-connected load balancing, nginx will try not to overload a busy application server with excessive requests, distributing the new requests to a less busy server instead.

    Least-connected load balancing in nginx is activated when theleast_conn directive is used as part of the server group configuration:

    upstream myapp1 {
        least_conn;
        server srv1.example.com;
        server srv2.example.com;
        server srv3.example.com;
    }

Session persistence

    Please note that with round-robin or least-connected load balancing, each subsequent client’s request can be potentially distributed to a different server. There is no guarantee that the same client will be always directed to the same server.

    If there is the need to tie a client to a particular application server —in other words, make the client’s session “sticky” or “persistent” in terms of always trying to select a particular server — the ip-hash load balancing mechanism can be used.

    With ip-hash, the client’s IP address is used as a hashing key to determine what server in a server group should be selected for the client’s requests. This method ensures that the requests from the same client will always be directed to the same server except when this server is unavailable.

    To configure ip-hash load balancing, just add theip_hash directive to the server (upstream) group configuration:

upstream myapp1 {
    ip_hash;
    server srv1.example.com;
    server srv2.example.com;
    server srv3.example.com;
}

Weighted load balancing

    It is also possible to influence nginx load balancing algorithms even further by using server weights.

    In the examples above, the server weights are not configured which means that all specified servers are treated as equally qualified for a particular load balancing method.

    With the round-robin in particular it also means a more or less equal distribution of requests across the servers — provided there are enough requests, and when the requests are processed in a uniform manner and completed fast enough.

    When the weight parameter is specified for a server, the weight is accounted as part of the load balancing decision.

    upstream myapp1 {
        server srv1.example.com weight=3;
        server srv2.example.com;
        server srv3.example.com;
    }

    With this configuration, every 5 new requests will be distributed across the application instances as the following: 3 requests will be directed to srv1, one request will Go to srv2, and another one — to srv3.

    It is similarly possible to use weights with the least-connected and ip-hash load balancing in the recent versions of nginx.


Health checks

    Reverse proxy implementation in nginx includes in-band (or passive)server health checks. If the response from a particular server fails with an error, nginx will mark this server as failed, and will try to avoid selecting this server for subsequent inbound requests for a while.

    The max_fails directive sets the number of consecutive unsuccessful attempts to communicate with the server that should happen during fail_timeout.By default, max_fails is set to 1. When it is set to 0, health checks are disabled for this server.Thefail_timeout parameter also defines how long the server will be marked as failed. Afterfail_timeout interval following the server failure, nginx will start to gracefully probe the server with the live client’s requests. If the probes have been successful, the server is marked as a live one.


Further reading

    In addition, there are more directives and parameters that control server load balancing in nginx,e.g . proxy_next_upstream,backup,down, and keepalive. For more information please check our reference documentation.

    Last but not least,application load balancing,application health checksactivity monitoring and on-the-fly reconfiguration of server groups are available as part of our paid NGINX Plus subscriptions.

    The following articles describe load balancing with NGINX Plus in more detail:


调试日志

  要开启调试日志,首先需要在配置nginx时打开调试功能,然后编译:

./configure --with-debug ...

  然后在配置文件中设置error_log的级别为debug

error_log  /path/to/log  debug;

  nginx的windows二进制版本总是将调试日志开启的,因此只需要设置debug的日志级别即可。

  注意,重新定义错误日志时,如过没有指定debug级别,调试日志会被屏蔽。下面的例子里,在server层中重新定义的日志就屏蔽了这个虚拟主机的调试日志:

error_log  /path/to/log  debug;

http {
    server {
        error_log  /path/to/log;
        ...

  为了避免这个问题,注释这行重新定义日志的配置,或者也给日志指定debug级别:

error_log  /path/to/log  debug;

http {
    server {
        error_log  /path/to/log  debug;
        ...

  另外,也可以只针对选定的客户端地址开启调试日志:

error_log  /path/to/log;

events {
    debug_connection   192.168.1.1;
    debug_connection   192.168.10.0/24;
}


配置文件中的计量单位

  容量可以用千字节(kK)和兆字节(mM)来描述,比如“8k”,“1m”。如果没有指定单位,容量以字节为单位。

  时间可以用分钟、小时、天等来描述:

s
m分钟
h小时
d
w
M月,30天
y年,365天

  比如,“1h 30m”,“1y 6M”。另外,在一些指令中,描述时间可以精确到毫秒精度(ms)。如果没有指定单位,时间以秒为单位。


nginx Windows版使用说明

  nginx的Windows版本使用原生Win32 API(非Cygwin模拟层)。当前nginx/Windows只使用select作为通知方法,所以不要期待它有很高的性能和扩展性。鉴于这点和一些已知问题,nginx/Windows目前还处于beta阶段。nginx/Windows和Unix版本相比,功能几乎已经齐全,除了XSLT过滤器、图像过滤器、GeoIP模块和嵌入Perl语言支持以外。

  安装nginx/Windows,需要下载最新的1.7.3开发版本,因为开发分支上包含了所有已知的问题修复,尤其是针对Windows版本的问题修复。解压缩下载得到的zip文件,进入nginx-1.7.3目录,运行nginx。下面给出一个在C盘根目录下安装的例子:

cd c:\
unzip nginx-1.7.3.zip
cd nginx-1.7.3
start nginx

  可以在命令行运行tasklist命令来查看nginx进程:

C:\nginx-1.7.3>tasklist /fi "imagename eq nginx.exe"

Image Name           PID Session Name     Session#    Mem Usage
=============== ======== ============== ========== ============
nginx.exe            652 Console                 0      2 780 K
nginx.exe           1332 Console                 0      3 112 K

  其中一个是主进程,另一个是工作进程。如果nginx没有启动,请查看logs\error.log文件以寻找失败原因。如果日志文件不存在,那失败原因会记录在Windows事件日志中。如果某次请求没有展示预想的页面,而是展示了错误页面,也请查看logs\error.log文件。

  nginx/Windows使用工作目录作为前缀将配置文件中设置的相对目录补齐。就上面安装的例子而言,工作目录应该是C:\nginx-1.7.3\(工作目录基本上与运行文件所在的目录相同)。配置文件中的目录请使用“/”,而不是“\”做目录分隔:

access_log   logs/site.log;
root         C:/web/html;

  nginx/Windows作为标准控制台应用运行,而不是系统服务。可以用下面的命令控制:

nginx -s stop快速退出
nginx -s quit优雅退出
nginx -s reload更换配置,启动新的工作进程,优雅的关闭以往的工作进程
nginx -s reopen重新打开日志文件
已知问题
  • 虽然可以启动若干工作进程运行,实际上只有一个进程在处理请求所有请求。
  • 一个工作进程只能处理不超过1024个并发连接。
  • 缓存和其他需要共享内存支持的模块在Windows Vista及后续版本的操作系统中无法工作,因为在这些操作系统中,地址空间的布局是随机的。
日后可能加强的功能
  • 作为系统服务运行。
  • 使用“I/O完成端口”作为事件模型。
  • 使用单工作进程多线程的模型。


转换rewrite规则

  共享站点的管理员,习惯于只在Apache下使用.htaccess文件配置所有信息,通常会将下面规则:

RewriteCond  %{HTTP_HOST}  example.org
RewriteRule  (.*)          http://www.example.org$1

  翻译成这样:

server {
    listen       80;
    server_name  www.example.org  example.org;
    if ($http_host = example.org) {
        rewrite  (.*)  http://www.example.org$1;
    }
    ...
}

  这种做法是错的,复杂而且低效。正确的方式是为example.org定义一个单独的服务器:

server {
    listen       80;
    server_name  example.org;
    return       301 http://www.example.org$request_uri;
}

server {
    listen       80;
    server_name  www.example.org;
    ...
}
在0.9.1版本(含)以前,可以这样实现重定向:
    rewrite      ^ http://www.example.org$request_uri?;

  再举一个例子,处理一个和刚才相反的逻辑:既不是来自example.com,又不是来自www.example.com

RewriteCond  %{HTTP_HOST}  !example.com
RewriteCond  %{HTTP_HOST}  !www.example.com
RewriteRule  (.*)          http://www.example.com$1

  应该按下面这样分开定义example.comwww.example.com和其他站点:

server {
    listen       80;
    server_name  example.com www.example.com;
    ...
}

server {
    listen       80 default_server;
    server_name  _;
    return       301 http://example.com$request_uri;
}
在0.9.1版本(含)以前,可以这样实现重定向:
    rewrite      ^ http://example.com$request_uri?;

转化混合规则

  典型的混合规则如下:

DocumentRoot /var/www/myapp.com/current/public

RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ %{DOCUMENT_ROOT}/system/maintenance.html [L]

RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^(.*)$ $1 [QSA,L]

RewriteCond %{REQUEST_FILENAME}/index.html -f
RewriteRule ^(.*)$ $1/index.html [QSA,L]

RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.*)$ $1/index.html [QSA,L]

RewriteRule ^/(.*)$ balancer://mongrel_cluster%{REQUEST_URI} [P,QSA,L]

  转换成nginx配置应该是这样:

location / {
    root       /var/www/myapp.com/current/public;

    try_files  /system/maintenance.html
               $uri  $uri/index.html $uri.html
               @mongrel;
}

location @mongrel {
    proxy_pass  http://mongrel;
}


WebSocket proxying

    To turn a connection between a client and server from HTTP/1.1 into WebSocket, theprotocol switch mechanism available in HTTP/1.1 is used.

    There is one subtlety however: since the "Upgrade" is ahop-by-hop header, it is not passed from a client to proxied server. With forward proxying, clients may use theCONNECT method to circumvent this issue. This does not work with reverse proxying however, since clients are not aware of any proxy servers, and special processing on a proxy server is required.

    Since version 1.3.13, nginx implements special mode of operation that allows setting up a tunnel between a client and proxied server if the proxied server returned a response with the code 101 (Switching Protocols), and the client asked for a protocol switch via the "Upgrade" header in a request.

    As noted above, hop-by-hop headers including "Upgrade" and "Connection" are not passed from a client to proxied server, therefore in order for the proxied server to know about the client’s intention to switch a protocol to WebSocket, these headers have to be passed explicitly:

location /chat/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

     A more sophisticated example in which a value of the "Connection" header field in a request to the proxied server depends on the presence of the "Upgrade" field in the client request header:

http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    server {
        ...

        location /chat/ {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值