已经是快到 2017 年,很多网站居然还不支持 HTTPS 访问。要知道 Chrome 可是要开始将 HTTP 站点标记为“不安全”了。那么这一篇就说一下如何最快速地将一个站点配置为 HTTPS 访问。

注意:一旦将你的网站配置为 HTTPS 访问,引用的所有脚本、图片、样式表等都必须是有效的 HTTPS 资源,否则页面将被标记为不安全。

首先,我们使用的组合是这样的:DNSPod + Let’s Encrypt 服务 + nginx。也就是说在本文的例子中,域名 NS 服务使用的是 DNSPod,证书是由 Let’s Encrypt 颁发的,服务器使用 nginx。

关于 nginx 的版本,建议是卸载服务器系统源中的版本,从 nginx 官方源安装,具体操作步骤参考 nginx 的官方文档。需要注意的是,nginx 在 Ubuntu 14.04 上不支持 HTTP2,具体原因和解决方法参考这里

第一件事情:在服务器上配置好你的域名,最简单的 nginx 配置如下。

server {
    server_name example.com;
    listen 80;
    rewrite ^(.*) https://example.com$1 permanent;
}

server {
    server_name example.com;
    listen 443 http2;
    ssl on;
    include /etc/nginx/ssl_params;
    ssl_certificate /home/user/example/keys/ssl/example.com.fullchain.cer;
    ssl_certificate_key /home/user/example/keys/ssl/example.com.key;

    include /etc/nginx/hsts_headers;

    location / {
        return 204;
    }
}

以上配置文件示例中,我们让 nginx 在 80 端口监听 HTTP,在 443 端口监听 HTTPS(并且使用 HTTP2),并将 HTTP 请求转向 HTTPS 地址。最终使用的证书和密钥存放在 /home/user/example/keys/ssl/ 目录中,这个路径你可以任意指定,只要 nginx 有权限读取对应文件就行。

此外,配置中还有两个 include 的文件 /etc/nginx/ssl_params/etc/nginx/hsts_headers。后者只是添加了一些 HSTS 头,内容如下。

add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;

关于 HSTS 头有什么作用,以及为什么要添加这些响应头,请参考维基百科的 HSTS 页面

/etc/nginx/ssl_params 这个文件是一些 SSL 相关的配置,一个简单的例子如下。

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_dhparam /etc/nginx/dhparams.pem; # See https://weakdh.org/sysadmin.html for more details
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
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";

其中 /etc/nginx/dhparams.pem 文件的生成以及 SSL ciphers 的选择请参考 Guide to Deploying Diffie-Hellman for TLS

编辑好配置文件后,可以按一般习惯存放在 /etc/nginx/sites-available/example.com,然后软链接到 /etc/nginx/sites-enabled/example.com。暂时先不要重启 nginx 服务,因为我们还没有申请证书,重启是肯定会失败的。

第二件事情就是去 DNSPod 将你的域名解析配置好,并生成 API token。域名解析应该不用多说,API token 是在通过 DNSPod 的 API 来操作 NS 记录时授权使用的,可以认为就是应用程序密码。关于如何新建一个 API token,请参考 DNSPod 的官方文档。这里个人建议是一台服务器上只需要申请一个 API token,名称要描述清楚 token 的用途和使用的机器,方便日后查询和管理。token 生成后只显示一次,这时候需要复制下来,供下一步使用。

第三步就是去 Let’s Encrypt 申请证书了,我们使用的是 acme.sh 工具。关于这个工具的优点有很多,我个人选择它的原因大概是:

  • 安装、使用、卸载都非常简单
  • 对于一个域名只需要手动操作一次,之后续期和重启服务器都是自动的

自动续期是很重要的一点,因为 Let’s Encrypt 颁发的都是 90 天有效的短期证书。

从零开始使用 acme.sh 的步骤如下。

# 首先安装这个工具
curl https://get.acme.sh | sh

# 安装路径在 ~/.acme.sh
# 安装好之后看一看命令是否可用,如果有问题,请手动修改你的 shell 配置文件

which acme.sh
# acme.sh: aliased to /home/user/.acme.sh/acme.sh

# 创建我们最终存放证书的路径
mkdir -p /home/user/example/keys/ssl

# 设置你的 DNSPod API token
# 注意:多数 shell 会记录你的操作历史,安全起见,操作完毕后请手动删除 ~/.zsh_history 或 ~/.bash_history 中对应的内容
export DP_Id="1234"
export DP_Key="sADDsdasdgdsf"

# 申请证书
# acme.sh 会自动调用 DNSPod API 去设置证书申请期间需要使用的 NS 记录,并会等待 120 秒让记录生效
acme.sh --issue --dns dns_dp -d example.com

申请完毕之后,acme.sh 会打印出所有相关文件的绝对路径。但是不要急着去使用这些文件,我们还要将它们安装到指定的位置。

说点多余的,其实这一步不是必须的,你完全可以直接使用这些文件。但是这些文件都存放在 ~/.acme.sh 中,之后 acme.sh 升级时,内部目录结构可能会改变,造成你的配置文件失效;并且通过额外的安装步骤,acme.sh 可以在续期后帮你自动重启 nginx 服务。后一点是很重要的,因为 nginx 只是在启动时读取证书和密钥,在续期后如果不重启,它还是使用旧的证书,你可能直到旧证书失效才会发现。

安装证书的步骤很简单。

acme.sh --installcert -d example.com --keypath /home/user/example/keys/ssl/example.key --fullchainpath /home/user/example/keys/ssl/example.com.fullchain.cer --reloadcmd "sudo /usr/sbin/service nginx reload"

只是将证书和密钥安装到指定的路径中,并且 reload nginx 配置。

需要注意的是,如果你的 sudo 命令需要密码,这里可能会有麻烦。但是也不要就因此直接用 root 用户来运行 acme.sh。你可以参考这篇文章,将当前用户配置为不需要输入 sudo 密码,或者配置为运行指定命令时不需要密码。

安装完证书后就可以访问你的网站了,按照前面的 nginx 配置,网站会返回 204 空响应,你可以通过 Chrome 开发工具的 Security 面板来检查网站的 HTTPS 证书。建议同时通过 SSL Server Test 来检查一下,根据检测结果来修正 SSL 相关设置,尽量达到 A 级或 A+。

最后,如果你想确认 acme.sh 会不会帮你网站的证书做自动续期,运行 crontab -l 来查看当前用户的定时任务,你应当会看到 acme.sh 自动添加的条目。这个任务会每天运行一次,如果一个域名的证书已经颁发超过 60 天,acme.sh 就会帮你自动续期。

本文中使用的例子是 DNSPod,但 acme.sh 还支持很多其他的 NS 服务提供商,具体请参考官方文档。

附一篇 nginx 相关配置的文章:本博客 Nginx 配置之安全篇 | JerryQu 的小站