Nginx配置WordPress与HSTS

本文介绍HSTS基本概念,以及在nginx上配置HSTS的方法,配置站点基于WordPress。

本博客(https://blog.lyz810.com)已开启HSTS,读者可以尝试访问http协议,并观察浏览器的行为。
一、背景介绍
HSTS(HTTP Strict Transport Security)是HTTP严格传输安全,它是全站HTTPS时的一个更安全的策略,并且对网站性能有一定的优化。
全站HTTPS是一个必然的趋势,目前全站HTTPS通常的做法是同时开启80和443端口,当请求访问的是http协议时,通过rewrite将请求301重定向到https协议的对应URI。这么做的缺点是当用户访问http协议的页面时,总会有一次301重定向,增加网络请求以及服务器负担。
使用HSTS后,浏览器在第一次访问http协议时,会通过rewrite重定向到https协议,并根据https协议头中设定的相关响应头缓存下HSTS配置,下次用户再访问该站点下的任意http页面,浏览器会自动通过307跳转到https对应的URI上,不需要向服务器多发送一次请求。
HSTS配置方法如下:
1.当客户端通过HTTPS发出请求时,在服务器返回的超文本传输协议响应头中包含Strict-Transport-Security字段。非加密传输时设置的HSTS字段无效。
2.Strict-Transport-Security有3个参数,max-age表示有效期(如max-age=31536000表示未来的一年内,访问该域名会强制使用https,其中数字表示一年的秒数);includeSubDomains表示子域名也强制使用https;preload主要用于加入preload列表使用。
preload列表是一个站点的列表,他将会被通过硬编码写入 Chrome 浏览器中,列表中的站点将会默认使用 HTTPS 进行访问,此外,Firefox 、Safari 、IE 11 和 Edge 也同样一份 HSTS 站点列表,其中包括了 Chrome 的列表
加入preload列表的条件:

  • 有一张有效的证书
  • 重定向所有的 HTTP 流量到 HTTPS ( HTTPS ONLY )
  • 全部子域名的流量均通过 HTTPS ,如果子域名的 www 存在的话也同样需要通过 HTTPS 传输。
  • 为https协议添加的响应头Strict-Transport-Securit内容必须满足:max-age必须大于18周,必须指定includeSubDomains和preload,如果从当前https站点重定向到其他的站点,那个站点也必须启用HSTS

加入HSTS preload list:https://hstspreload.appspot.com/
二、Nginx配置

server {
  ......
  if ( $https != "on" ) {#https变量的值在https协议下为on,否则为空字符串
     rewrite ^(.*) https://$host$1 permanent;#301定向到https协议的相同URI,主要是首次访问本站的请求使用
     break;
  }

  location / {
     try_files $uri $uri/ /index.php$args;#此行是WordPress使用的,它会将请求都交给/index.php处理
     add_header Strict-Transport-Security max-age=86400000;#添加响应头,过期时间1000天,即1000天之内,浏览器会自动将本站http协议转为https协议
  }

  location ~ \.php$ {
     fastcgi_pass localhost:9000;#fastcgi监听9000端口,其他fastcgi配置写在了公用配置文件中
     add_header Strict-Transport-Security max-age=86400000;#由于正则的优先级更高,所以所有动态的页面均会走这里,需要添加响应头
   }
}

三、使用HSTS的弊端
用户只要访问一次站点,就会在max-age指定的时间内,强制访问https协议,在此期间,如果网站https出现了问题(如证书配置错误),用户就无法进行访问,没有配置HSTS时,证书错误用户可以选择忽略错误继续访问。
如果想要在过期时间内再次开启http访问,是无法做到的(对没有访问过站点的用户可行,只要访问过,过期时间前用户不能访问http协议),如果加入到HSTS Preload List中,就更没有办法启用http访问。
四、浏览器支持度
Chromium和Google Chrome从4.0.211.0版本开始支持HSTS
Firefox 4及以上版本
Opera 12及以上版本
Safari从OS X Mavericks起
Internet Explorer和Microsoft Edge从Windows 10开始支持

在CentOS7上的nginx中部署Let’s Encrypt免费证书

本文介绍如何在CentOS7上的nginx中部署Let‘s Encrypt免费证书。

之前一直使用的是沃通免费证书,最近看到网上的一些消息,担心Firefox和Chrome会取消它的根证书,所以准备逐步替换为Let’s Encrypt免费证书。目前已在https://demo.lyz810.com中使用,未来可能会逐步将其他域替换为Let’s Encrypt免费证书。
下面将操作步骤记录一下,以便后续替换时查阅。

一、安装certbot
文档:https://certbot.eff.org/#centosrhel7-nginx
$ sudo yum install epel-release
$ sudo yum install certbot

二、为域名申请一个证书
-w后面是站点根目录
-d后面是站点域名,如果多个域名,可以使用多个-d参数,每个-d参数跟一个域名,-d之间用空格分开
certbot certonly --webroot -w /website/lyz810-main/demo/ -d demo.lyz810.com

1.提示输入邮箱,用于紧急通知以及密钥恢复
2.阅读文档,选Agree即可

如果成功证书和私钥会保存在/etc/letsencrypt/live/demo.lyz810.com/中

三、nginx配置证书
ssl_certificate /etc/letsencrypt/live/demo.lyz810.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/demo.lyz810.com/privkey.pem;
重启nginx服务器

四、证书自动续期
证书有效期为90天,所以需要写一个定时任务

#minute   hour    day  month  week    command
0         0,12    *    *      *       certbot renew > /var/log/certbot.log & echo certbot last renew at `date` >> /var/log/certbot.log

在每天0点和12点会更新一次证书,并将结果保存到/var/log/certbot.log日志中。
注意:
如果设置cron或systemd任务,建议一天执行两次(如果没有到需要更新的时间段内,运行此命令不会更新证书)

nginx配置https服务器

本文介绍如何将nginx配置成一个https服务器。

配置https服务器,需要在listen指令后面添加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;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ...
}

服务器证书是一个公开的实体,它将会被发送到所有连接服务器的客户端上。私钥是一个安全的实体,应该有着严格的访问控制权限,但必须能被nginx读取,私钥和证书可以用同一个文件存储:

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

这种情况下,权限控制仍然是严格的,即使证书和私钥存在同一个文件里,但只有证书会被发送到客户端。
ssl_protocols和ssl_ciphers指令限制了使用的加密算法和版本,只能用比较健壮的加密算法。默认情况下,nginx使用“ssl_protocols TLSv1 TLSv1.1 TLSv1.2”和“ssl_ciphers HIGH:!aNULL:!MD5”,因此一般不需要特意指定。注意,这个默认值已经改变了多次,详见文章底部的兼容性。
https服务器优化
SSL操作会增加CPU的消耗。在多核CPU的操作系统中应使用多个工作进程,并且数量不少于可用CPU的核心数。CPU最密集的操作是SSL握手阶段。有两种方法使这种操作降到最低:第一是开启keep_alive,让多个请求复用一个连接;第二是复用SSL session以避免并行的连接和并发的连接进行SSL握手。通过ssl_session_cache指令配置SSL session,这些session存储在缓存中供工作进程共享使用。1M的缓存里有4000个会话信息。默认的缓存超时时间是5分钟,可以通过ssl_session_timeout指令增加。下面是一个运行在多核操作系统使用了10M缓存的简单配置方法:

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证书链
一些浏览器会要求证书是可信的CA颁布,而另一些浏览器却不理会。这种情况的产生是由于使用的证书是中间人进行颁发的,而这个颁发机构并没有加入到浏览器的信任根上,这时应该将信任根添加到证书中:
$ cat www.example.com.crt bundle.crt > www.example.com.chained.crt
最终的配置文件如下:

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

如果证书和bundle的顺序反了,nginx将会在启动时报错:

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

浏览器总是保存中间的可信证书,所以很多浏览器不会理会没有证书链的问题。
配置一个包含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指令完整的配置,无法实现一个server上运行HTTPS和HTTP。现在增加了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在建立连接时nginx还不知道主机名,所以会提供默认的证书。
原始粗暴的解决方法是,通过监听不同的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;
    ...
}

一个证书多个名称
有其他的办法共享同一个IP地址,建立多个HTTPS服务器,但所有的方法都有些弊端。一个方法是将多个名称加到证书的SubjectAltName域中,如www.example.com和www.example.org,但长度有限制。
另一种方法是使用通配符名称,如*.example.org。这种证书可以认证所有的子域名,但只能有一级。它能匹配www.example.org但不能匹配example.org和www.sub.example.org。这两种方法可以混合使用。如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;
    ...
}

兼容性

  • The SNI support status has been shown by the “-V” switch since 0.8.21 and 0.7.62.
  • listen指令的ssl参数从0.7.14被支持,0.8.21版本之前它只能与default参数一同定义
  • SNI has been supported since 0.5.32.
  • 共享SSL会话缓存功能从0.5.6版本开始支持
  • 1.9.1及以后的版本:默认SSL协议为TLSv1, TLSv1.1, and TLSv1.2(如果openssl库支持)
  • 0.7.65, 0.8.19及更高版本:默认的SSL协议为SSLv3, TLSv1, TLSv1.1, and 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”