<!-- review: finished -->

<a id="ssl-config"></a>

# SSL配置

要配置HTTPS服务器，必须在 [server](https://cn.angie.software//angie/docs/configuration/modules/http/index.md#server) 块的监听套接字上启用 [ssl](https://cn.angie.software//angie/docs/configuration/modules/http/index.md#listen) 参数，并且需要指定服务器证书和私钥文件的位置：

```nginx
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.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
#...
}
```

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

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

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

指令 [ssl_protocols](https://cn.angie.software//angie/docs/configuration/modules/http/http_ssl.md#ssl-protocols) 和 [ssl_ciphers](https://cn.angie.software//angie/docs/configuration/modules/http/http_ssl.md#ssl-ciphers) 可用于限制连接，仅包含SSL/TLS的强版本和密码。默认情况下，Angie使用：

```nginx
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
```

因此，通常不需要显式配置它们。

<a id="https-optimization"></a>

## HTTPS服务器优化

SSL操作消耗额外的CPU资源。在多处理器系统上，应运行多个 [worker processes](https://cn.angie.software//angie/docs/configuration/modules/core.md#worker-processes)，数量不能少于可用的CPU核心数。最耗CPU的操作是SSL握手。减少每个客户端的这些操作数量有两种方法：第一种是启用 [keepalive](https://cn.angie.software//angie/docs/configuration/modules/http/index.md#keepalive-timeout) 连接，通过一个连接发送多个请求，第二种是重用SSL会话参数，以避免并行和后续连接的SSL握手。会话存储在一个由 [ssl_session_cache](https://cn.angie.software//angie/docs/configuration/modules/http/http_ssl.md#ssl-session-cache) 指令配置的共享SSL会话缓存中。1MB的缓存大约包含4000个会话。默认缓存超时为5分钟。可以使用 [ssl_session_timeout](https://cn.angie.software//angie/docs/configuration/modules/http/http_ssl.md#ssl-session-timeout) 指令增加。以下是一个针对具有10MB共享会话缓存的多核系统优化的配置示例：

```nginx
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.2 TLSv1.3;
        ssl_ciphers         HIGH:!aNULL:!MD5;
    #...
```

<a id="certificate-chaining"></a>

## SSL证书链

某些浏览器可能会对由知名证书颁发机构签署的证书提出警告，而其他浏览器可能不会。这是因为颁发机构使用了一个中间证书来签署服务器证书，而该中间证书并不在某个特定浏览器分发的知名受信任证书机构的证书库中。在这种情况下，颁发机构提供了一组链式证书，这些证书应与签署的服务器证书串联在一起。服务器证书必须在组合文件中位于链式证书之前：

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

生成的文件应与 [ssl_certificate](https://cn.angie.software//angie/docs/configuration/modules/http/http_ssl.md#ssl-certificate) 指令一起使用：

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

如果服务器证书和捆绑证书的顺序连接错误，Angie将无法启动并显示错误信息：

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

因为Angie尝试使用捆绑证书的第一个证书而不是服务器证书来使用私钥。

浏览器通常会存储它们收到的由受信任机构签署的中间证书，因此实际使用的浏览器可能已经拥有所需的中间证书，并且可能不会对未发送链式捆绑的证书提出警告。为了确保服务器发送完整的证书链，可以使用 **openssl** 命令行工具，例如：

```console
$ 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的主题，该证书由一个颁发者签署，该颁发者本身是证书#2的主题，该证书由知名颁发者ValiCert, Inc.签署，其证书存储在浏览器的内置证书库中。

如果没有添加证书捆绑，只有服务器证书#0将会显示。

<a id="compact-server"></a>

## 单个HTTP/HTTPS服务器

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

```nginx
server {
    listen              80;
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;
#...
}
```

<a id="name-based-https-servers"></a>

## 基于名称的HTTPS服务器

配置两个或多个在单个IP地址上监听的HTTPS服务器时，会出现常见问题：

```nginx
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请求之前建立，而Angie并不知道请求的服务器名称。因此，它只能提供默认服务器的证书。

<a id="https-separate-ips"></a>

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

```nginx
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;
#...
}
```

<a id="an-ssl-certificate-with-multiple-names"></a>

## 具有多个名称的SSL证书

还有其他方法可以在多个HTTPS服务器之间共享一个IP地址。然而，它们都有各自的缺点。一种方法是使用在 `SubjectAltName` 证书字段中包含多个名称的证书，例如 `www.example.com` 和 `www.example.org` 。但是，`SubjectAltName` 字段的长度是有限的。

另一种方法是使用通配符名称的证书，例如 `*.example.org`。通配符证书保护指定域的所有子域，但仅限于一个级别。该证书匹配 `www.example.org`，但不匹配 `example.org` 和 `www.sub.example.org`。这两种方法也可以结合使用。证书可以在 `SubjectAltName` 字段中包含精确名称和通配符名称，例如 `example.org` 和 `*.example.org`。

最好将具有多个名称的证书文件及其私钥文件放在 `http` 配置级别，以便在所有服务器中继承它们的单一内存副本：

```nginx
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;
#...
}
```

<a id="sni"></a>

## 服务器名称指示

在单个IP地址上运行多个HTTPS服务器的更通用的解决方案是TLS服务器名称指示扩展（SNI，[RFC 6066](https://datatracker.ietf.org/doc/html/rfc6066.html)），该扩展允许浏览器在SSL握手期间传递请求的服务器名称，因此，服务器将知道它应该使用哪个证书进行连接。SNI目前被大多数现代浏览器支持，但一些旧的或特殊的客户端可能不会使用。

如果Angie是使用SNI支持构建的，则在使用 `-V` 开关运行时，Angie将显示此信息：

```console
$ angie -V
...
TLS SNI support enabled
...
```

然而，如果启用SNI的Angie动态链接到没有SNI支持的OpenSSL库，Angie将显示警告：

> Angie 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
