启用免费且自动更新的泛域名SSL证书

经过数次跳票之后,Let's Encrypt2018年3月13日开始提供支持泛域名的SSL证书了,每次颁发证书的有效时间是3个月,因此Let's Encrypt提供了一个自动颁发和更新SSL证书的工具acme.sh,使用下来感觉比收费的还要方便。

安装acme.sh

acme.sh 实现了 acme 协议, 可以从 letsencrypt 生成免费的证书。下面我们以CentOS为例进行说明:

安装系统依赖

acme.sh需要curl、cron和socat的依赖支持,使用下面的任务进行安装:

yum update -y &&  yum -y install curl cron socat

对于官方不支持的CentOS版本(如5.x),可以手动下载和编译上述包进行安装。
PS:CentOS 5.x可以使用socat v1.7.2.4,安装方法可以参考:socat的安装与使用

开始安装 acme.sh

可以使用下面的命令安装:

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

安装脚本将所有的文件安装到 ~/.acme.sh/目录下,并自动创建一个定时任务,每天0:00自动检测所有的证书,如果过期了就会自动更新证书。

设置成自动更新acme.sh(强烈建议)

因为Let's Encrypt的证书更新方式经常变更,所以需要保持 acme.sh 到最新的版本,使用下面的命令设置acme.sh为自动更新:

 ~/.acme.sh/acme.sh  --upgrade  --auto-upgrade

如果不需要自动更新,可以使用下面的命令:

~/.acme.sh/acme.sh --upgrade  --auto-upgrade  0

证书自动签发/续签

设置默认CA服务

acme.sh 从 v3 开始将ZeroSSL设置为默认的CA服务,如果是其他服务则需要使用 --server 参数指定,支持的服务列表如下:

Short Name ACME server URL
letsencrypt https://acme-v02.api.letsencrypt.org/directory
letsencrypt_test https://acme-staging-v02.api.letsencrypt.org/directory
buypass https://api.buypass.com/acme/directory
buypass_test https://api.test4.buypass.no/acme/directory
zerossl https://acme.zerossl.com/v2/DV90
sslcom https://acme.ssl.com/sslcom-dv-rsa, https://acme.ssl.com/sslcom-dv-ecc

也可以使用 ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt 命令将Let's Encrypt设置为默认CA服务。

生成证书

生成证书的方式就两种:http方式dns方式,相对来说我更喜欢dns方式,这种方式可以使用dns解析商的API自动进行域名的验证等操作,非常方便。目前支持 cloudflare, dnspod, cloudxns, godaddy 以及 ovh 等数十种解析商的自动集成,如果你的域名不是使用的这些解析商的话,智能使用http方式进行手动验证了。

这里以常用的dnspod来介绍如果使用dns方式来生成证书,首先在DNSPOD用户中心-安全设置中开启API Token,然后创建一个API Token,并记住ID和Token,执行下面的命令:

export DP_Id="<your_dnspod_id>"
export DP_Key="<your_dnspod_token>"
~/.acme.sh/acme.sh --issue --dns dns_dp -d javatang.com -d *.javatang.com

上面的DP_Id和DP_Key是dnspod.cn API定义的变量名,--dns参数后面的dns_dp也指定了服务商为dnspod.cn,其他服务商的API名称见https://github.com/Neilpang/acme.sh/wiki/dnsapi。相同的ID和Key只要指定一次就可以了,acme.sh会自动将其保存在account.conf文件中。

后面-d参数用于生成证书的域名,如果想要生成泛域名的SSL证书必须按照上面的例子那样设定两次-d参数,第一次必须是主域名,不可以直接写泛域名的格式,后面一次是*.javatang.com泛域名的格式。如果不需要泛域名的SSL证书的话,只要指定一次-d参数就可以了。

执行上面的命令之后,acme.sh会自动校验域名的有效性并像Let's Encrypt请求SSL证书,成功之后会将证书放在~/.acme.sh/目录下面,但一定要注意,不要在nginx/apache中直接使用此目录下面的证书文件,这是因为脚本升级之后此目录会发生变化,会造成引用错误。正确的做法是再执行复制并安装证书。

复制并安装证书

使用--installcert命令可以将证书复制到固定的位置,并保证在更新证书之后自动重启nginx/apache,这里一nginx为例,执行的脚本如下:

~/.acme.sh/acme.sh --installcert -d javatang.com --key-file /etc/nginx/conf/cert/javatang.com.key --fullchain-file /etc/nginx/conf/cert/javatang.cer --reloadcmd "service nginx force-reload"

-d参数表示需要复制的域名名称,如果是泛域名的话直接使用主域名。

--key-file--fullchain-file参数分别表示所要复制的key和fullchain文件的位置和文件名,nginx/apache配置文件中所引用的SSL文件级为这里所设置的路径。

最后一个参数--reloadcmd也非常重要,这里表示证书更新之后自动重启nginx/apache的命令,这样才能保证更新之后的证书有效。我一开始将这个参数设置错误了,导致证书到期更新之后没有应用于nginx。

常用操作命令

查看当前证书列表

可以使用~/.acme.sh/acme.sh --list命令查看当前的证书列表,如下图所示:
file

除了显示域名和子域名信息之外,还可以显示使用的哪种证书,创建的日期和下次续签的日期。

手动更新证书

可以使用 ~/.acme.sh/acme.sh -r -d <Main_Domain> 命令手动更新指定域名的证书,也可以使用 ~/.acme.sh/acme.sh --renew-all 更新所有的证书。

删除域名证书

如果因为误操作需要删除域名证书话,可以使用 --remove参数。

首先使用 ~/.acme.sh/acme.sh --list 查看当前的证书列表,然后使用 ~/.acme.sh/acme.sh --remove -d <Main_Domain> 删除对应的域名证书,最后可以再使用--list 参数查看是否删除成功。

PS:虽然acme.sh将所有的操作放在了 ~/.acme.sh/ 目录下,但不建议直接删除该目录下的域名目录。

配置nginx/apache

最后不要忘记了还要再nginx/apache中引用上面的SSL证书,这里以nginx为例,配置文件如下:

server {
    listen 443 ssl;
    server_name javatang.com *.javatang.com;

     ssl on; 
    ssl_certificate   cert/javatang.com.cer;
    ssl_certificate_key  cert/javatang.com.key;
    ssl_session_timeout 5m; 
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_prefer_server_ciphers on;
}

再强调一下:这里引用的SSL证书一定不要是acme.sh原始下载的证书,而是使用--installcert命令复制的证书。

使用acme.sh还有一个好处就是不需要担心证书过期的问题,因为脚本会自动更新证书,非常方便。

设置执行结果通知(强烈建议)

为了确保SSL证书及时更新,强烈建议设置自动结果通知的功能。

设置通知等级和模式

acme.sh有如下的通知等级(默认为2):

0: disabled, no notification will be sent.
1: send notification only when there is an error. No news is good news.
2: send notification when a cert is successfully renewed, or there is an error
3: send notification when a cert is skipped, renewed, or error. You will receive notification every night with this level.

还有如下通知模式(默认为0):

0: Bulk mode. Send all the domain's notifications in one message(email)
1: Cert mode. Send a message for every single cert. You may receive a bulk of emails in one night.

可以通过 acme.sh --set-notify --notify-level 2 --notify-mode 0 命令同事设置等级和模式,当然也可以单独设置。

设置通知方式

acme.sh提供了非常丰富的通知方式,包括邮件、钉钉消息、QQ消息等方式,这里推荐通过sendgrid.com接口发送邮件通知的方式。
首先在sendgrid.com注册账号,然后生成API Key,最后通过下面的命令进行配置:

export SENDGRID_API_KEY="<your sendgrid api key>"
export SENDGRID_FROM="<your sender email>"
export SENDGRID_TO="<your receiver email>"
export SENDGRID_FROM_NAME="Email Sender Name"

~/.acme.sh/acme.sh --set-notify  --notify-hook sendgrid

可以通过SENDGRID_FROM_NAME在不同服务器上设置成不同的发件人名称,方便进行排查。
最后可以通过acme.sh --set-notify --notify-hook sendgrid命令进行设置,如果成功的话会提示success并自动发送一份测试的邮件。

更多信息可以参考官方notify文档

查看证书的情况

最后可以通过 https://crt.sh 查询指定域名的证书详情。

常见问题

安装的时候出现 error code: 35

在有的服务器中遇到了下面的错误提示:

Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: 35

升级本地的openssl版本,可以使用下面的命令进行升级:

wget -c https://www.openssl.org/source/openssl-1.1.0-latest.tar.gz
tar xzvf openssl-1.1.0-latest.tar.gz
cd openssl-1.1.0*
./config --prefix=/usr/local/openssl
make && make install
mv /usr/bin/openssl /usr/bin/openssl.old -f
mv /usr/lib64/openssl /usr/lib64/openssl.old -f
mv /usr/lib64/libssl.so /usr/lib64/libssl.so.old -f
mv /usr/include/openssl /usr/include/openssl.old -f
ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl
ln -s /usr/local/openssl/include/openssl /usr/include/openssl
ln -s /usr/local/openssl/lib/libssl.so /usr/lib64/libssl.so
echo "/usr/local/openssl/lib" >> /etc/ld.so.conf
ldconfig -v

Let's Encrypt证书申请频率的限制

  • 同一个主域名一周之内只能申请50个证书
  • 每个账号下每个域名每小时申请验证失败的次数为5次
  • 每周只能创建5个重复的证书,即使是通过不同的账号进行创建
  • 每个账号同一个IP地址每3小时最多可以创建10个证书
  • 每个多域名(SAN) SSL证书(不是通配符域名证书)最多只能包含100个子域
  • 更新证书没有次数的限制,但是更新证书会受到上述重复证书的限制

访问SSL证书之后的系统偶尔会出现卡顿的问题

对服务器资源和网络状况进行排除之后,在nginx的error.log文件中发现有很多类似下面的错误信息:

2019/03/22 17:28:42 [crit] 20807#0: *249015212 SSL_shutdown() failed (SSL: error:140E0197:SSL routines:SSL_shutdown:shutdown while in init) while closing request, client: 100.200.70.100, server: 0.0.0.0:443

查阅了相关的资料发现,是nginx和openssl的版本太低,需要保证nginx的版本在1.9.12以上,openssl的版本在1.1.0以上。

出现tls_process_client_hello:version too low错误

使用低版本的IE浏览器会无法访问https,在nginx的error.log文件中出现下面的错误信息:

SSL_do_handshake() failed (SSL: error:1417D18C:SSL routines:tls_process_client_hello:version too low) while SSL handshaking

通过命令wget --secure-protocol=SSLv3 -O - https://www.javatang.com/ 进行测试,结果如下:

OpenSSL: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
Unable to establish SSL connection.

这是因为低版本的IE浏览器采用了SSLv3进行访问,而OpenSSL从1.1.0开始默认取消了SSLv3,即使在nginx的ssl_protocols配置项中增加SSLv3也是无效的,可以通过下面几种方法进行解决:
(1)编译OpenSSL v1.1.x的时候config的时候增加enable-ssl3 enable-ssl3-method参数,编译Nginx的时候configure的时候增加--with-openssl-opt="enable-ssl3 enable-ssl3-method",然后重新编译OpenSSL和Nginx。不过不建议这样操作,因为SSLv3有安全漏洞,可以采用下面的方法。
(2)在Nginx中通常会采用80端口做301跳转到433端口,可以取消跳转同时保留80和433两个端口的访问,或者判断浏览器是IE或采用了SSLv3的时候进行跳转,设置如下:

server {
    listen 80;

    set $oldclient 0;
    if ($http_user_agent ~* "MSIE") {
        set $oldclient 1;
    }
    if ($ssl_protocol = SSLv3) {
        set $oldclient 1;
    }
    if ($oldclient = 0) {
        rewrite ^(.*) https://$host$1 ;
        break;
    }
}

但我试了一下无法达到使用SSLv3访问SSL的时候自动跳转到80端口,直接就报错了。

使用一段时间无法自动更新证书

如果安装成功更新都是正常的,过了一段时间发现无法自动更新证书了,首先使用 ~/.acme.sh/acme.sh --version 查看acme.sh的版本是否是最新的版本,如果不是的话使用 ~/.acme.sh/acme.sh --upgrade 升级到最新版再试。

参考资料:

acme.sh官方说明
acme.sh参数列表
获取免费的 Let’s Encrypt 泛域名 SSL 证书
Let’s Encrypt免费泛域名SSL证书申请教程
Let'sEncrypt 免费通配符/泛域名SSL证书添加使用教程
Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: 35
OpenSSL: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failur
Can't wget from github, sslv3 handshake error
Using acme.sh with nginx
SSL_shutdown() failed (SSL: error:140E0197:SSL routines:SSL_shutdown:shutdown while in init) while SSL handshaking
SSL error in nginx log
Nginx SSL_do_handshake() failed SSL: error:1417D18C:SSL
SSL too low a protocol?
How can I know the protocol versions supported at OpenSSL1.1.0g
Compiling OpenSSL 1.1.0f with SSLv3
Redirect users connecting with SSLv3 within nginx
SSL证书的自动化管理
无法更新域名 #2911

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注