个人网站迁移到 HTTPS

编辑于2018年07月27日

近期,我将个人主页(http://www.whezh.com)和博客(http://blog.whezh.com)迁移到了 HTTPS,这篇文章记录了我迁移的全过程,包括 SSL 证书的申请以及 Nginx 相应的配置,其中 HTTPS 证书使用的是 Let's Encrypt 提供的免费证书。值得庆幸的是,今天年初 Let's Encrypt 已经支持申请泛域名证书,省去了一些麻烦。

安装证书

在 Let's Encrypt 网站上介绍了如何安装证书,大致是 SSH 到服务器,然后安装 ACME Client(ACME Client 可以为我们自动完成证书的签发),并列出了可用的 ACME Client,Let's Encrypt 推荐我们使用 Certbot

我一开始使用了 certbot-auto 并且成功安装了证书,但是后面在设置自动续期的时候遇到了一些问题,使我放弃使用 certbot-auto 转而使用了 acme.sh,最终完成了迁移工作。我先介绍使用 acme.sh 的经历,后面我会简要说明使用 certbot-auto 的过程及遇到的问题。

使用 acme.sh

acme.sh 的文档十分详细,安装和使用起来也十分的简单,只需要几个命令即可。它支持很多平台,我的服务器是 Ubuntu。

安装

acme.sh 支持两种安装方式,一种是在线安装,一种是通过 Git 安装。我使用的是在线安装的方式,即执行下面的命令:

$ curl https://get.acme.sh | sh

在安装的时候有警告,提示安装 socat,apt-get install socat

安装过程经历了三个步骤:

  1. 将 acme.sh 安装到 ~/.acme.sh/ 目录下。
  2. 创建一个别名:alias acme.sh=~/.acme.sh/acme.sh
  3. 创建一个计划任务,每天的 0:00 会检查并自动更新证书。

如果出现 acme.sh 没找到的情况,可以手动执行第 2 步,我当时就是这样。

生成证书

acme.sh 实现了 acme 协议支持的所有验证协议,一般有两种方式验证: HTTP 和 DNS 验证,两种验证都是为了证明域名所有权,就像百度、谷歌的站点统计服务。

HTTP 方式需要在签发证书时指定域名, 并指定域名所在的网站根目录。 acme.sh 会全自动的生成验证文件, 并放到网站的根目录, 然后自动完成验证,验证完成后会删除验证文件,整个过程没有任何副作用。如果使用了 apache 或者 nginx,acme.sh 还可以智能的读取配置文件自动完成验证,只需要指定对应的模式即可,不会修改任何配置。

我使用的是 DNS 验证方式,acme.sh 提供了两种模式,一种是手动,另一种则是自动。其中手动方式需要我们手动执行命令并设置 DNS 解析,自动方式则需要 DNS 服务商提供 API,acme.sh 目前已经支持家服务商,具体用法见文档

因为我使用的是阿里云服务器,根据文档的说明,首先需要设置环境变量:

$ export Ali_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
$ export Ali_Secret="jlsdflanljkljlfdsaklkjflsa"

Ali_keyAli_Secret 会被保存到 ~/.acme.sh/account.conf 中用于下次使用。

设置完环境变量之后,执行下面的命令完成证书的签发:

$ acme.sh --issue --dns dns_ali -d *.whezh.com

签发完成后,得到下图所示结果,代表成功生成证书。

issue

证书默认安装在 ~/.acme.sh/<domain> 下,但我们不能直接使用此目录下的证书,这里面的文件是内部使用的,而且目录结构可能会变化。正确的做法是使用 installcert 命令将证书安装到指定目录。

$ acme.sh --installcert -d *.whezh.com --key-file /etc/letsencrypt/whezh.com/whezh.com.key --fullchain-file /etc/letsencrypt/whezh.com/fullchain.cer --reloadcmd "service nginx restart”

其中 Nginx 中 ssl_certificate 配置使用 fullchain.cer,而不是 <domain>.cer,否则 SSL Labs 的测试会报 Chain issues Incomplete 错误。
reloadcmd 参数指定了证书自动更新后执行的重载命令。
这里指定的所有参数都会被自动记录下来,并在将来证书自动更新以后,被再次自动调用。

到这里,证书的安装工作就全部完成了,接下来只需要配置服务器即可。

使用 certbot-auto

下载安装 certbot-auto

$ wget https://dl.eff.org/certbot-auto
$ chmod a+x certbot-auto

签发证书

$ sudo ./certbot-auto certonly -d *.whezh.com --manual --preferred-challenges dns  --server https://acme-v02.api.letsencrypt.org/directory

上述命令指定完之后,弹出提示,添加 TXT 解析记录。

WX20180727-154935

我们需要登录域名 DNS 服务商网站,在网页上添加一条 TXT 解析记录。添加完成后,可以使用 nslookup -type=TXT _acme-challenge.whezh.com 命令验证是否添加成功。

验证成功后,回车继续执行即可成功申请到证书。

WX20180727-155208

一开始,成功申请到证书后,配置了一下 Nginx 可以正常使用。但是,当我想设置自动更新证书时,执行 certbot-auto renew 命令提示错误,大致意识是模式不正确,缺少一点东西,但是也不想多折腾,就果断放弃了。

Nginx 配置

证书实现完成后,就需要进行服务器配置了。这里需要说明的是,我将博客网址设为了二级域名 blog.whezh.com,个人主页使用的是 www.whezh.com,我需要缺省的 whezh.com 重定向到 www.whezh.com,并且强制所有网页使用 HTTPS。

个人主页配置:

# www.whezh.com.conf
server {
    listen 80;

    server_name whezh.com www.whezh.com;

    return 301 https://www.whezh.com$request_uri;
}

server {
    listen 443;

    server_name whezh.com;

    return 301 https://www.whezh.com$request_uri;
}

server {
    listen 443;

    server_name www.whezh.com;
	
    ssl on;
    ssl_certificate /etc/letsencrypt/whezh.com/fullchain.cer;
    ssl_certificate_key /etc/letsencrypt/whezh.com/whezh.com.key;
    location / {
        root /home/hezhou/www/home;
        index index.html;
    }
}

博客配置:

# blog.whezh.com.conf
server {
    listen 80;

    server_name blog.whezh.com;

    return 301 https://blog.whezh.com$request_uri;
}

server {
    listen 443;

    server_name blog.whezh.com;
    root /var/www/ghost/system/nginx-root;

    ssl on;
    ssl_certificate /etc/letsencrypt/whezh.com/fullchain.cer;
    ssl_certificate_key /etc/letsencrypt/whezh.com/whezh.com.key;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:2368;       
    }

    location ~ /.well-known {
        allow all;
    }

    client_max_body_size 50m;
}