配置HTTPS服务器

HTTPS服务器优化
SSL证书链
单个HTTP/HTTPS服务器
基于名称的HTTPS服务器
     具有多个名称的SSL证书
     服务器名称指示
兼容性

要配置HTTPS服务器,必须在服务器块中的监听套接字上启用ssl参数,并指定服务器证书私钥文件的位置:

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

服务器证书是公共实体,发送给连接到服务器的每个客户端。私钥是安全实体,应存储在受限制访问的文件中,但必须对nginx的主进程可读。私钥也可以存储在与证书相同的文件中:

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

在这种情况下,文件访问权限也应受限制。虽然证书和密钥存储在一个文件中,但只有证书会发送给客户端。

指令ssl_protocolsssl_ciphers可用于限制连接仅包括SSL/TLS的强版本和密码。默认情况下,nginx使用“ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3”和“ssl_ciphers HIGH:!aNULL:!MD5”,因此通常不需要显式配置它们。请注意,这些指令的默认值已多次更改

HTTPS服务器优化

SSL操作消耗额外的CPU资源。在多处理器系统上,应运行多个worker进程,不少于可用CPU核心数。最耗CPU的操作是SSL握手。可以通过两种方式最小化每个客户端的这些操作数量:一种是启用keepalive连接,通过一个连接发送多个请求,另一种是重用SSL会话参数,以避免为并行和后续连接进行SSL握手。会话存储在由ssl_session_cache指令配置的SSL会话缓存中,该缓存在工作进程之间共享。1兆字节的缓存大约包含4000个会话。默认缓存超时为5分钟。可以使用ssl_session_timeout指令增加它。以下是针对具有10兆字节共享会话缓存的多核系统进行优化的示例配置:

worker_processes auto;

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

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

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

SSL证书链

某些浏览器可能会抱怨由知名证书颁发机构签名的证书,而其他浏览器可能会接受证书而没有问题。这是因为颁发机构使用一个中间证书对服务器证书进行签名,而该中间证书不在特定浏览器分发的已知受信任证书颁发机构的证书库中。在这种情况下,颁发机构提供了一组链式证书,应将这些链式证书连接到签名的服务器证书之前。服务器证书必须在组合文件中的链式证书之前出现:

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

生成的文件应该在ssl_certificate指令中使用:

server {
    listen              443 ssl;
    server_name         www.example.com;
    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
...

在使用SNI测试配置时,重要的是将-servername选项指定为openssl默认不使用SNI。

在这个例子中,www.GoDaddy.com服务器证书#0的主题(“s”)由一个发行者(“i”)签名,该发行者本身是证书#1的主题,由一个发行者签名,该发行者本身是证书#2的主题,由一个被知名发行者ValiCert, Inc.签名的证书存储在浏览器内置的证书库中(那就是杰克建造的房子里)。

如果未添加证书绑定,只会显示服务器证书#0。

一个单独的HTTP/HTTPS服务器

可以配置一个同时处理HTTP和HTTPS请求的单个服务器:

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.7.14之前,SSL不能选择性地为单个侦听套接字启用,如上所示。SSL只能通过ssl指令为整个服务器启用,因此无法设置单个HTTP/HTTPS服务器。为了解决这个问题,添加了listen指令的ssl参数。在现代版本中使用ssl指令是不鼓励的。

基于名称的HTTPS服务器

当配置两个或多个HTTPS服务器监听单个IP地址时,通常会出现一个常见问题:

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

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

使用这种配置,浏览器接收默认服务器的证书,即www.example.com,而不管请求的服务器名称如何。这是由SSL协议行为引起的。SSL连接建立在浏览器发送HTTP请求之前,nginx不知道请求的服务器名称。因此,它只能提供默认服务器的证书。

解决该问题的最古老且最稳健的方法是为每个HTTPS服务器分配一个单独的IP地址:

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

server {
    listen          192.168.1.2:443 ssl;
    server_name     www.example.org;
    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 ssl;
    server_name     www.example.com;
    ...
}

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

服务器名称指示

在单个IP地址上运行多个HTTPS服务器的更通用解决方案是TLS服务器名称指示扩展(SNI,RFC 6066),它允许浏览器在SSL握手期间传递请求的服务器名称,因此,服务器将知道应该使用哪个证书进行连接。 SNI当前由大多数现代浏览器支持,尽管一些旧的或特殊的客户端可能不使用。

只能传递域名作为SNI,但是如果请求包含字面IP地址,一些浏览器可能错误地传递服务器的IP地址作为其名称。 不能依赖这一点。

为了在nginx中使用SNI,它必须在构建nginx二进制文件的OpenSSL库中得到支持,并且在运行时被动态链接到的库中也得到支持。 如果OpenSSL是在0.9.8f版本或更高版本中使用配置选项“--enable-tlsext”构建的,则OpenSSL支持SNI。 从OpenSSL 0.9.8j开始,默认情况下启用此选项。 如果使用了支持SNI的nginx构建,则当使用“-V”开关运行nginx时,nginx会显示这一点:

$ 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

兼容性

作者:Igor Sysoev
编辑:Brian Mercer