使用 acme.sh 在单台 Linux 服务器或者集群上自动化申请和更新 https 证书

acme.sh 是一个自动化申请和更新 https 证书的脚本,非常地好用;但在使用过程中也发现了一些不足,Ricky 在此提供一个解决方案。

什么是 acme.sh ?

acme.sh 实现了 acme 协议,可以从 letsencrypt 生成免费的证书,详情请看 acme.sh 在 github 上的首页和 github 上的中文介绍

如何安装和使用 acme.sh(以 CentOS Linux 6 / 7 为例)

如果您看 github 上的中文介绍您也能了解这部分的内容,但我这里会说一些 github 上所没有提及的(比如安装失败提示 Download error 时该如何解决)。

1 、安装 acme.sh :

安装 acme.sh 的命令只有一条:

[root@host ~]# curl  https://get.acme.sh | sh

但 Ricky 建议您这么安装:

[root@host ~]# yum -y install nss
[root@host ~]# yum -y update nss
[root@host ~]# curl  https://get.acme.sh | sh
[root@host ~]# /root/.acme.sh/acme.sh  --upgrade  --auto-upgrade

( 1 )执行 /root/.acme.sh/acme.sh –upgrade –auto-upgrade 的目的是开启 acme.sh 的自动更新。目前由于 acme 协议和 letsencrypt CA 都在频繁的更新,因此 acme.sh 也需要经常更新以保持同步。

( 2 )对于一些版本比较旧的 CentOS Linux(如 CentOS Linux 6.2 )而言,如果不升级 nss 这个包,在安装 acme.sh 时可能会报以下 Download error 的错误:

[root@host ~]# curl  https://get.acme.sh | sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   705  100   705    0     0    355      0  0:00:01  0:00:01 --:--:--  2357
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  164k  100  164k    0     0  31028      0  0:00:05  0:00:05 --:--:-- 69529
[Thu Nov  8 21:52:26 CST 2018] Installing from online archive.
[Thu Nov  8 21:52:26 CST 2018] Downloading https://github.com/Neilpang/acme.sh/archive/master.tar.gz
[Thu Nov  8 21:52:27 CST 2018] Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: 35
[Thu Nov  8 21:52:27 CST 2018] Download error.
[root@host ~]#

执行 yum -y install nss 和 yum -y update nss 后上述问题即可解决,再次安装 acme.sh 就不会报错了:

[root@host ~]# curl  https://get.acme.sh | sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   705  100   705    0     0    227      0  0:00:03  0:00:03 --:--:--  2365
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  164k  100  164k    0     0   4887      0  0:00:34  0:00:34 --:--:-- 15699
[Thu Nov  8 21:55:02 CST 2018] Installing from online archive.
[Thu Nov  8 21:55:02 CST 2018] Downloading https://github.com/Neilpang/acme.sh/archive/master.tar.gz
[Thu Nov  8 21:55:04 CST 2018] Extracting master.tar.gz
[Thu Nov  8 21:55:06 CST 2018] It is recommended to install socat first.
[Thu Nov  8 21:55:06 CST 2018] We use socat for standalone server if you use standalone mode.
[Thu Nov  8 21:55:06 CST 2018] If you don't use standalone mode, just ignore this warning.
[Thu Nov  8 21:55:06 CST 2018] Installing to /root/.acme.sh
[Thu Nov  8 21:55:06 CST 2018] Installed to /root/.acme.sh/acme.sh
[Thu Nov  8 21:55:06 CST 2018] Installing alias to '/root/.bashrc'
[Thu Nov  8 21:55:06 CST 2018] OK, Close and reopen your terminal to start using acme.sh
[Thu Nov  8 21:55:06 CST 2018] Installing alias to '/root/.cshrc'
[Thu Nov  8 21:55:07 CST 2018] Installing alias to '/root/.tcshrc'
[Thu Nov  8 21:55:07 CST 2018] Installing cron job
[Thu Nov  8 21:55:07 CST 2018] Good, bash is found, so change the shebang to use bash as preferred.
[Thu Nov  8 21:55:10 CST 2018] OK
[Thu Nov  8 21:55:10 CST 2018] Install success!
[root@host ~]#

安装成功后,在 crontab -l 里您能看到如下任务计划(以下任务计划每天凌晨 0 点 31 分执行一次,相关 crontab 操作请点击这里):

31 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null

github 上还列举了几种安装方式,详情请点击这里

2 、生成证书:

生成证书最重要的一步就是要验证域名的所有权(验证这个域名到底是不是您的,不然任何人都可以去生成 www.baidu.com 、www.taobao.com 和 www.qq.com 这些网站的证书来做钓鱼网站了),验证域名的所有权主要有两种方式:

( 1 )通过 DNS 来验证(在 DNS 解析里添加一条 TXT 记录,第三方验证服务器会去查询这条 TXT 记录是否存在),在 DNS 解析里添加一条 TXT 记录又分为手动和自动两种:

  • 允许 acme.sh 使用域名解析商提供的 API 自动添加 TXT 记录(不一定支持所有域名解析商的 API ,也不是所有的域名解析商都有相关 API );
  • 手工添加 TXT 记录(麻烦,达不到自动化运维的要求)。

( 2 )在代码目录里放置一个验证文件(第三方验证服务器会通过 http 的方式来访问您的代码目录,看其中是否有这个验证文件)。

综上所述,“ 在代码目录里放置一个验证文件 ” 这种验证方式是最方便的,这种方式生成证书的命令如下:

[root@host ~]# /root/.acme.sh/acme.sh --issue -d mydomain.com -d www.mydomain.com --webroot /home/wwwroot/mydomain.com/

或者:

[root@host ~]# /root/.acme.sh/acme.sh --issue -d www.mydomain.com --webroot /home/wwwroot/mydomain.com/

其中:

  • mydomain.com 和 www.mydomain.com 是您的域名;
  • /home/wwwroot/mydomain.com/ 是您网站的代码根目录。

只需要指定域名,并指定域名所在的网站根目录,acme.sh 就会全自动地生成验证文件,并放到网站的根目录,然后自动完成验证,最后会聪明地删除验证文件,整个过程没有任何副作用。

验证完成后,证书就会签发下来了,但此时还需要执行一个安装证书的命令。

3 、安装证书:

前面证书生成以后,接下来需要把证书 copy 到真正需要用它的地方。

注意,默认生成的证书都放在安装目录下:~/.acme.sh/ ,请不要直接使用此目录下的文件,例如:不要直接让 Nginx / Apache 的配置文件使用这下面的文件,这里面的文件都是内部使用,而且目录结构可能会变化。

正确的使用方法是使用 –installcert 命令来安装证书,并指定目标位置,然后证书文件会被 copy 到相应的位置,例如:

[root@host ~]# /root/.acme.sh/acme.sh  --installcert  -d  www.mydomain.com   \
               --key-file   /usr/local/nginx/conf/cert/www.mydomain.com.key \
               --fullchain-file /usr/local/nginx/conf/cert/www.mydomain.com.pem \
               --reloadcmd  "/usr/local/nginx/sbin/nginx -s reload"

经实践证明,使用命令 /usr/local/nginx/sbin/nginx -s reload 是可以让 Nginx 重新加载新的证书的(实验的 Nginx 版本号为 1.15.6 ,当前最新版本)。

如果您是想让 Nginx 多监听一个 443 端口,那我建议您先关闭 Nginx 的进程后再开启(相关 Nginx 的操作请点击这里)。Nginx 上有关于 https 和 443 端口的配置我建议您手工配置即可(今后 acme.sh 会自动更新证书文件并重新加载 Nginx )。

4 、更新证书:

目前证书在 60 天以后会自动更新,您无需任何操作(还记得上文中提到的 crontab -l 里的任务计划么?)。今后有可能会缩短这个时间,不过都是自动的,您不用关心。

5 、删除证书:

执行以下删除命令,再删除掉相关文件夹即可:

[root@host ~]# /root/.acme.sh/acme.sh --remove -d www.mydomain.com
[root@host ~]# rm -rf /root/.acme.sh/www.mydomain.com

在使用过程中 Ricky 遇到的问题:

首先非常感谢您能耐心地看到这里,接下来才是本文的重点。

在使用 acme.sh 时,虽然 “ 在代码目录里放置一个验证文件 ” 这种验证方式是最方便的,但是相比于 “ 通过 DNS 来验证 ” 的验证方式来说它有一个明显的缺点 —— 不支持集群。

假设您有一个域名是 www.mydomain.com ,这个域名会首先解析到 1.1.1.1 这个 IP 地址上,而 1.1.1.1 实际上是一台负载均衡设备(比如 A10 、F5 或者 HAProxy ),负载均衡设备再往后才是两台 Web 服务器 2.2.2.2 和 3.3.3.3( 如 Tomcat 或者 PHP 等 )。如果您在 2.2.2.2 和 3.3.3.3 上均安装了 acme.sh 并分别执行了 “ 在代码目录里放置一个验证文件 ” 这种验证方式的生成证书的命令,那么这个时候会发生什么呢?

假设 2.2.2.2 上的 acme.sh 生成的验证文件是 123 ,而 3.3.3.3 上的 acme.sh 生成的验证文件是 456 ,当第三方验证服务器通过 http 的方式来访问您的验证文件 123 时,负载均衡设备可能会将此次请求转给 3.3.3.3 ,那么此时验证失败 ……

所以在使用集群的时候反而是 “ 通过 DNS 来验证 ” 会更方便一点。但如果我的域名解析商没有提供相关的 DNS API ,我也不想手工添加 TXT 记录,而我同时还用着集群,就没有解决方法了吗?有的。

通过观察发现,acme.sh 会在您代码目录的 /.well-known/acme-challenge/ 文件夹下放置验证文件,那么我们可以通过反向代理来完成验证。比如:

  1. 我们专门创建一台安装有 acme.sh 的服务器 4.4.4.4 ,然后让所有 http://www.mydomain.com/.well-known/acme-challenge/xxxx 的 http 请求都转到 4.4.4.4 这台服务器上;
  2. 此时证书会生成到 4.4.4.4 这台服务器上,我们再在 2.2.2.2 和 3.3.3.3 上放置一个 Linux Shell 脚本,该脚本会自动去 4.4.4.4 上下载证书、自动跟现有的证书进行 MD5 比对,如果发现证书文件已经更新则自动 reload Nginx 即可。

详细步骤如下:

1 、专门创建一台服务器 4.4.4.4 ,安装好  acme.sh 。

2 、在 4.4.4.4 上安装一个 Nginx ,并配置以下 server 域:

server {
    listen       8081;
    server_name  4.4.4.4;
    root /www/ssl/;    # 记得创建该文件夹
}

从安全的角度出发,您不应该将该 8081 端口公开到互联网上(防止证书被别人下走),应该使用防火墙对该端口做一个基于源 IP 地址的限制,仅允许 2.2.2.2 和 3.3.3.3 来访问该端口(如果服务器均处于内网则可忽略):

iptables -A INPUT -s 2.2.2.2 -p tcp --dport 8081 -j ACCEPT
iptables -A INPUT -s 3.3.3.3 -p tcp --dport 8081 -j ACCEPT
iptables -A INPUT -p tcp --dport 8081 -j DROP

注意:如果您有自己的运维平台的话,可以自动获得所有服务器的 IP 地址,然后定时对该服务器的防火墙配置进行刷新即可。

3 、配置反向代理:

( 1 )如果您的负载均衡设备 1.1.1.1 具有反向代理的功能,那么可以在负载均衡设备上配置,以 HAProxy 为例:

acl dns_check path_reg -i ^/.well-known/acme-challenge/
use_backend dns_check_backend if dns_check

backend dns_check_backend
        mode    http
        reqideny      ^[^:\ ]*\ .*/(root\.exe\?|cmd\.exe\?|default\.ida\?)
        balance roundrobin
        server local 4.4.4.4:8081 maxconn 30000 check

        contimeout     10000
        srvtimeout      5000
        fullconn 99999
        redispatch
        retries 3

        option forwardfor
        option httpclose

( 2 )如果您的负载均衡设备不支持反向代理,那么可以在 2.2.2.2 和 3.3.3.3 上安装一个 Nginx ,然后这么配置:

server {
    listen       80;
    server_name  _;

    ......

    location ~ "/.well-known/acme-challenge/" {
       proxy_pass https://4.4.4.4:8081;
    }

    location / {
        rewrite ^(.*)  https://$host$1 permanent;
    }
}

这样,所有 http://www.mydomain.com/.well-known/acme-challenge/xxxx 的 http 请求都转到 4.4.4.4 这台服务器上了。

4 、在 4.4.4.4 上生成和安装证书:

请使用如下生成证书的命令:

[root@host ~]# /root/.acme.sh/acme.sh  --issue  -d www.mydomain.com  --webroot /www/ssl/

请使用如下安装证书的命令:

[root@host ~]# /root/.acme.sh/acme.sh  --installcert  -d  www.mydomain.com   \
               --key-file   /www/ssl/www.mydomain.com.key \
               --fullchain-file /www/ssl/www.mydomain.com.pem \
               --reloadcmd  "echo $THREE_LEVEL_DOMAIN - cert update . >> /www/ssl/log/cert.log"

这样,证书就会被安装到 /www/ssl/ 目录,方便 2.2.2.2 和 3.3.3.3 前来下载证书,同时 reloadcmd 命令也被设置成一个往 /www/ssl/log/cert.log 文件写入日志的命令。

5 、在 2.2.2.2 和 3.3.3.3 上使用 Linux Shell 脚本来定时更新证书并重新加载 Nginx :

假设您是将证书配置在 2.2.2.2 和 3.3.3.3 的 Nginx 上的,那么我们需要将证书下载到 2.2.2.2 和 3.3.3.3 上,同时今后证书还能自动更新,并自动重新加载 Nginx(即使用一个 Linux Shell 脚本来实现)。

2.2.2.2 和 3.3.3.3 的 Nginx 上如何配置证书呢?请看文章《 CentOS Linux 6 / 7 编译安装 Nginx 》的 “ 2、修改 Nginx 的配置文件,让其支持 https ” 部分。

如果 1.1.1.1 使用的是 HAProxy ,那么在 HAProxy 上配置证书也是可以的,请看文章《 CentOS Linux 6 / 7 编译安装 HAProxy 》的 “ 1、修改 HAProxy 的配置文件,让其支持 https ” 部分。同时,如下脚本需要进行相应的修改。

编写脚本,在命令行界面输入:

[root@host ~]# vi /root/update_cert.sh

键入小写字母 i ,进入编辑模式,将以下脚本复制粘贴进去(请根据实际需要进行相应地修改):

#!/bin/bash

if [ ! -f "update_cert.list" ] ; then
        echo update_cert.list not found !
        exit
fi
cert_of_list=`cat update_cert.list`

cd /usr/local/nginx/conf/cert/
is_reload="false"

for cert in $cert_of_list ; do
        wget https://4.4.4.4:8081/$cert.pem -O $cert.pem.new 
        wget https://4.4.4.4:8081/$cert.key -O $cert.key.new

        if [ -s "$cert.pem.new" -a -s "$cert.key.new" ] ; then

                if [ -f "$cert.pem" -a -f "$cert.key" ] ; then

                        pem_file_old=`md5sum $cert.pem | awk -F ' ' '{print $1}'`
                        pem_file_new=`md5sum $cert.pem.new | awk -F ' ' '{print $1}'`

                        key_file_old=`md5sum $cert.key | awk -F ' ' '{print $1}'`
                        key_file_new=`md5sum $cert.key.new | awk -F ' ' '{print $1}'`

                        if [ "$pem_file_old" != "$pem_file_new" -o "$key_file_old" != "$key_file_new" ] ; then
                                mv -f $cert.pem.new $cert.pem
                                mv -f $cert.key.new $cert.key

                                is_reload="true"

                                echo $(date "+%F %H:%M") - $cert changed ! >> /root/update_cert.log
                        else
                                rm -f $cert.pem.new $cert.key.new

                                #echo
                                #echo pem_file_old=$pem_file_old , pem_file_new=$pem_file_new
                                #echo key_file_old=$key_file_old , key_file_new=$key_file_new
                                echo $(date "+%F %H:%M") - $cert no changed . >> /root/update_cert.log
                        fi
                else
                        mv -f $cert.pem.new $cert.pem
                        mv -f $cert.key.new $cert.key

                        echo $(date "+%F %H:%M") - $cert new cert ! >> /root/update_cert.log
                fi
        else
                echo $(date "+%F %H:%M") - $cert download error ! >> /root/update_cert.log
        fi
done

if [ "$is_reload" == "true" ] ; then
        /usr/local/nginx/sbin/nginx -s reload
        echo $(date "+%F %H:%M") - nginx reloaded ! >> /root/update_cert.log
else
        echo $(date "+%F %H:%M") - nginx not reloaded . >> /root/update_cert.log
fi

按一次 ESC 键退出编辑模式,然后键入 “ :wq ” 保存并退出。

其中:

  1. update_cert.list 是配置文件,里面存放的是要更新证书的域名的名称(一个域名一行),请执行以下命令以写入配置:
    [root@host ~]# echo "www.mydomain.com" > /root/update_cert.list
  2. 目录 /usr/local/nginx/conf/cert/ 是 2.2.2.2 和 3.3.3.3 上证书存放的位置,
  3. /root/update_cert.log 是日志文件,
  4. /usr/local/nginx/sbin/nginx -s reload 是重新加载 Nginx 的命令。

6 、将 update_cert.sh 添加到 crontab 任务计划,并手工执行一次该脚本:

[root@host ~]# echo "51 3 * * * sh /root/update_cert.sh" >> /var/spool/cron/root
[root@host ~]# sh /root/update_cert.sh

现在,证书就会同步到 2.2.2.2 和 3.3.3.3 上了!(请注意上述脚本中的 is_reload 变量是在什么时候才等于 true 的,请根据实际需要进行相应地修改)

总结:

至此,acme.sh 脚本算是完美了。如果您没有使用集群,但是域名比较多,也可以使用上述方法来部署 acme.sh ,这样 acme.sh 只需要在一台服务器上安装即可(不用每台服务器都安装,方便统一管理)。

打赏作者
这里是 “ CCIE 工程师社区 ” 官方的捐款通道,您是否可以考虑请我们喝杯咖啡呢?

您的支持将鼓励我们继续创作!

[微信] 扫描二维码打赏

[支付宝] 扫描二维码打赏

Was this article helpful?

Related Articles

Leave A Comment?

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据