前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >国庆节前端技术栈充实计划(1):使用Nginx配置HTTPS 服务器

国庆节前端技术栈充实计划(1):使用Nginx配置HTTPS 服务器

作者头像
疯狂的技术宅
发布2019-03-27 15:29:31
9240
发布2019-03-27 15:29:31
举报
文章被收录于专栏:京程一灯京程一灯

要配置HTTPS NGINX 服务器,必须在配置文件 server 块中的监听指令 listen后启用 ssl参数,并且指定服务器证书 ssl_certificate 和私钥 ssl_certificate_key的位置:

代码语言:javascript
复制
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;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ...
}

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

代码语言:javascript
复制
ssl_certificate     www.example.com.cert;
    ssl_certificate_key www.example.com.cert;

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

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

HTTPS 服务器优化

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

代码语言:javascript
复制
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;
        ssl_ciphers         HIGH:!aNULL:!MD5;
        ...

SSL 证书链

有些浏览器可能警示由知名证书颁发机构签名的证书,而其他浏览器却能无问题的接受这些证书。这是因为这些证书颁发机构使用了中间证书来签署服务器证书,所签署的证书不存在于特定浏览器发行时内置的可信证书颁发机构颁发的证书库中。在这种情况下,颁发机构提供一组与颁发的服务器证书(根证书)串接的捆绑证书,并让服务器证书(根证书)出现在合并后文件(证书链)的捆绑证书之前:

代码语言:javascript
复制
$ cat www.example.com.crt bundle.crt www.example.com.chained.crt

生成的证书链文件应该放在 ssl_certificate指令之后:

代码语言:javascript
复制
server {
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.chained.crt;
    ssl_certificate_key www.example.com.key;
    ...
}

如果根证书和捆绑证书使用了错误的链接顺序,nginx将会启动失败并显示如下错误信息:

代码语言:javascript
复制
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命令行通用程序,例如:

代码语言:javascript
复制
$ 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_s_
   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")签发,而签发者("i")本身又是#1号证书的请求者("s"),它的证书签发者是#2号证书的请求者,它请求的证书由知名发布者ValiCert,Inc.签发,其证书存储在浏览器的内置证书库中(如同英国童谣 The House That Jack Built 讲述的一样)。

如果捆绑证书没有被添加到证书链,那只有 #0 号证书会被展示出来。

单个 HTTP/HTTPS 虚拟主机

现在,在单个nginx虚拟主上可以配置同时处理 HTTP 和 HTTPS 请求:

代码语言:javascript
复制
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地址时会出现常见问题:

代码语言:javascript
复制
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地址:

代码语言:javascript
复制
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

It is better to place a certificate file with several names and its private key file at the http level of configuration to inherit their single memory copy in all servers: 最好在配置文件的 http区块中放置具有多个名称的证书文件及其私钥文件,以在所有其下的虚拟主机服务器中继承其单个内存副本:

代码语言:javascript
复制
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限制了它支持的浏览器。 目前支持从以下浏览器版本及其后:

  • Opera 8.0;
  • IE 7.0 (Windows Vista及更高版本);
  • Firefox 2.0 及其他使用 Mozilla Platform rv:1.8.1 的浏览器;
  • Safari 3.2.1 (Windows版本支持Windows Vista及更高版本);
  • Chrome (Windows版本支持Windows Vista及更高版本).

只有域名可以在SNI中传递,然而如果请求包含IP地址,一些浏览器可能错误地把服务器的IP地址作为其名称进行传递,我们不能依赖于这个。

为了在nginx中使用SNI,必须在构建nginx的OpenSSL库以及运行时的动态链接库中支持它。OpenSSL从0.9.8f版本支持SNI,如果在编译时给 config增加了 --enable-tlsext选项;从OpenSSL 0.9.8j版本开始默认启用此选项。如果nginx是以支持SNI方式构建的,当使用“-V”参数运行时,nginx会显示这一信息:

代码语言:javascript
复制
$ nginx -V
...
TLS SNI support enabled
...

但是,如果启用SNI的nginx与没有SNI支持的OpenSSL库动态链接,nginx将显示警告:

代码语言:javascript
复制
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" 参数可以查看SNI支持状态;
  • 从 0.7.14 版本开始支持 listen指令的 ssl参数;更早版本到 0.8.21 只能使用'default'参数指定;
  • 从 0.5.23 版本开始支持SNI;
  • 从 0.5.6 版本开始支持共享SSL会话缓存;
  • 从 1.9.1 及其后版本,默认的SSL协议是:TLSv1, TLSv1.1, TLSv1.2(如果OpenSSL库支持)
  • 从 0.7.65, 0.8.19 及其后版本开始,默认的SSL协议是:SSLv3, TLSv1, TLSv1.1, TLSv1.2(如果OpenSSL库支持)
  • 0.7.64, 0.8.18 及更早版本,默认的SSL协议是:SSLv2, SSLv3, and 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

往期精选文章

使用虚拟dom和JavaScript构建完全响应式的UI框架

扩展 Vue 组件

使用Three.js制作酷炫无比的无穷隧道特效

一个治愈JavaScript疲劳的学习计划

全栈工程师技能大全

WEB前端性能优化常见方法

一小时内搭建一个全栈Web应用框架

干货:CSS 专业技巧

四步实现React页面过渡动画效果

让你分分钟理解 JavaScript 闭包



小手一抖,资料全有。长按二维码关注京程一灯,阅读更多技术文章和业界动态。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-10-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 京程一灯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
SSL 证书
腾讯云 SSL 证书(SSL Certificates)为您提供 SSL 证书的申请、管理、部署等服务,为您提供一站式 HTTPS 解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档