2016-7-13 更新了一下,现在是评分是A+了。

SSL Lab的评分

楔子

Debian的一个不好的地方是官方仓库里的软件版本都好老,对于我这个激进人士来说不好😕。正好看了子龙山人安利HTTPS的帖子,觉得里面说的不用HTTPS的原因就是我一直以来不愿意搞的借口。痛定思痛,立马折腾。

Debian上安装主线版本的Nginx

所用的命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 移除已经安装的nginx。
apt-get purge nginx
apt-get autoremove

# 添加nginx官方的Debian仓库,这里有主线版本,针对的是Debian的Jessie (8.x)。
(cat <<-SRC
# nginx mainline repository
deb http://nginx.org/packages/mainline/debian/ jessie nginx
deb-src http://nginx.org/packages/mainline/debian/ jessie nginx
SRC
) > /etc/apt/sources.list.d/nginx-mainline.list

# 添加key。
wget http://nginx.org/keys/nginx_signing.key
apt-key add nginx_signing.key

# 安装Nginx。
apt-get update
apt-get install nginx

方法来自这里,命令做了修改。

添加HTTPS支持

首先,教程里面推荐的letsencrypt在我的VPS上无法安装,表现是运行了安装命令后CPU和内存占用飞涨,然后报错,有几次VPS直接关机了。搜索了一下,在这里发现了acme-tiny,就用它了。配置HTTPS可以分为准备工作、申请证书、 安装证书和定时更新。

准备工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 安装acme-tiny
git clone https://github.com/diafygi/acme-tiny.git

# 新建存放证书的文件夹
mkdir -p /www/certs/

# 建立Let's Encrypt账户的私钥
openssl genrsa 4096 > /www/certs/account.key

# 生成域名的私钥
openssl genrsa 4096 > /www/certs/domain.key

# 生成域名的私钥
# 下面一行是针对单一域名的,如果你像我一样同时用带www和不带www的,需要用后面的那个。
# openssl req -new -sha256 -key domain.key -subj "/CN=chriszheng.science" > /www/certs/domain.csr
# 这是对多个域名的
openssl req -new -sha256 -key /www/certs/domain.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:chriszheng.science,DNS:www.chriszheng.science")) > /www/certs/domain.csr

# 新建用于验证域名的challenges文件夹
mkdir -p /www/challenges/

Nginx需要相关的配置,这块我不是很熟悉,而且自己也弄不好,只能贴出来我的配置跟各位分享了:

Update:

后来发现我根本就不懂Nginx的配置方法,纯粹是瞎写的。challenge的目的是为了验证域名的所有权,需要保证网站里的/.well-known/acme-challenge/下的文件是可以访问的,也就是说
http://chriszheng.science/.well-known/acme-challenge/wSMr1m2H9yXUxehaho3YT_G7ysSc6G3uz7JZgHocgdo
是可以访问到的。我犯的错误是不懂alias和root的区别。最初的写法写了一个root /www/challenges/;,这样的话Nginx会去/www/challenges/目录里/.well-known/acme-challenge/,就是说找/www/challenges/.well-known/acme-challenge/,这明显不对,而应该用alias。从上面的连接也学到了root可以不写最后的/alias必须写,这点也算是它们的区别了。修改后的代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
listen [::]:80;

location /.well-known/acme-challenge/ {
default_type "text/plain";
alias /www/challenges/;
}

# location / {
# return 301 https://$host$request_uri;
# }
}

最后三行注释掉的内容在配置好了之后可以取消注释,那是用来将HTTP重定向到HTTPS的。

开始申请

1
python ./acme-tiny/acme_tiny.py --account-key /www/certs/account.key --csr /www/certs/domain.csr --acme-dir /www/challenges/ > /tmp/signed.crt

需要注意的是申请证书是有次数限制的,每周对同一个域名最多只能申请5次证书。

Update:

解决了上面的问题下面的步骤已经不需要了。

在我的VPS上,可能是配置问题,这一步总会出现无法验证域名的问题,广泛查找资料也无解,所以我就把acme_tiny.py里相关的代码注释了。

安装证书

1
2
3
wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > /tmp/intermediate.pem
cat /tmp/signed.crt /tmp/intermediate.pem > /www/certs/chained.pem
wget -O - https://letsencrypt.org/certs/isrgrootx1.pem https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem https://letsencrypt.org/certs/letsencryptauthorityx1.pem https://www.identrust.com/certificates/trustid/root-download-x3.html | tee -a /www/certs/ca-certs.pem> /dev/null

Update:

这里需要解释一下。前两行是获取了letsencrypt的交叉签名的证书,以前我弄了一个x1的,似乎不对,后面一行代码来自这里,好像是用于OCSP stapling的。

Nginx的配置也需要修改,我现在用的是下面的样子,这个参考了很多配置但我也不是很懂😾:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

server_name chriszheng.science www.chriszheng.science;

ssl_certificate /www/certs/chained.pem;
ssl_certificate_key /www/certs/domain.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits.
ssl_dhparam /etc/ssl/certs/dhparam.pem;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';

# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;

# OCSP Stapling ---
# fetch OCSP records from URL in ssl_certificate and cache them
ssl_stapling on;
ssl_stapling_verify on;

## verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /www/certs/ca-certs.pem;

resolver 8.8.8.8;

root /www/hexo;
index index.html index.htm;

location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ /index.html;
expires 1h;
# Uncomment to enable naxsi on this location
# include /etc/nginx/naxsi.rules
}
}

server {
listen 80;
listen [::]:80;

location /.well-known/acme-challenge/ {
default_type "text/plain";
alias /www/challenges/;
}

location / {
return 301 https://$host$request_uri;
}

自动化脚本

新建/etc/cron.monthly/renew_cert内容为:

2017-1-11 Update: 注意,cron的脚本不能加后缀,否则不执行。

1
2
3
4
5
6
7
8
9
#!/bin/sh
openssl genrsa 4096 > /www/certs/domain.key
openssl req -new -sha256 -key /www/certs/domain.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:chriszheng.science,DNS:www.chriszheng.science")) > /www/certs/domain.csr
python /root/acme-tiny/acme_tiny.py --account-key /www/certs/account.key --csr /www/certs/domain.csr --acme-dir /www/challenges/ > /tmp/signed.crt || exit
wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > /tmp/intermediate.pem
cat /tmp/signed.crt /tmp/intermediate.pem > /www/certs/chained.pem
wget -O - https://letsencrypt.org/certs/isrgrootx1.pem https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem https://letsencrypt.org/certs/letsencryptauthorityx1.pem https://www.identrust.com/certificates/trustid/root-download-x3.html | tee -a /www/certs/ca-certs.pem> /dev/null
service nginx reload
exit 0

问题和解决方案

兼容性

用现在的配置基本就抛弃了WinXP+IE6的用户,这种用户基本不会访问我的网站,问题不大。

混合内容

全站HTTPS要求所用的CDN也支持HTTPS,国内的CDN很多不支持,所以我把一些资源变成了本地的。

在写法上,要把http://cdn.bootcss.com变成//cdn.bootcss.com,就是和当前站的协议一样,如果所用的CDN也支持HTTPS就不会出错了。

第三方服务

对于我的静态站来说,第三方服务比如Disqus的需要做相应的设置和迁移。

Update: Disqus搞的不好,我的很多评论的URL坏了,还不能改,肯定是他们的系统有问题。

不能通过IP访问网站

这是一个缺陷,浏览器会提示不安全。

Update:

搜索效果变差

谷歌骗我,我今天看了一下,发现展现量和访问量都变低了。

Update:

我似乎明白了,变低是因为流量都重定向到HTTPS的站上了,将那个站也添加到谷歌里面就好了。

结语

虽然折腾上了,还是有很多不懂,只能先用起来再慢慢学习和完善。感谢无数的「先行者」不停的晓之以理,动之以情的宣传HTTPS打消了我的顾虑。贴吧的雅丶涵一直在普及证书知识,在此表示感谢。

当然,最要感谢的是Let’s Encrypt