使用 Certbot 的 Docker 镜像签发和自动续期 Let's Encrypt 证书

平时自己用的几台 VPS 配置都比较低,也就是跑点自己折腾着玩的东西,就懒得上 K8S 了,所以基本都是用 Certbot 来获取证书。

泛域名证书

自己用基本都是直接申请泛域名证书,Let’s Encrypt 真是太良心了。Certbot 支持的 DNS 服务商没有 acme.sh 多,如果 Certbot 不支持你使用的 DNS 服务商的话,可以试试 acme.sh。

我这里用的是 Cloudflare,其他服务商的就替换相应的镜像,并根据镜像说明调整相应配置就可以了。

获取 API Token

在 Cloudflare 创建 API Tokien,使用【编辑区域 DNS】的模板就可以,如果有多个不同域名,记得选上相应的区域。把获取到的 API Token 放到 cloudflare.ini 中。

cloudflare.ini
1
2
# Cloudflare API token used by Certbot
dns_cloudflare_api_token = hwdObJq7XbX-LtDXwLi1cdn0HiXmZHjy0Wr3MGGH

签发新证书

证书相关的东西我一般都放在 /opt/nginx/ssl 下面,脚本里的邮箱也可以不加。当然,直接 docker run 也可以,毕竟只要跑一次就可以。

get_cert.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
DOMAIN1=oxn.me
DOMAIN2=*.oxn.me
EMAIL=*@example.com

docker run --rm -v $PWD:/etc/letsencrypt \
-v $PWD/logs:/var/log/letsencrypt \
-v $PWD/cloudflare.ini:/cloudflare.ini \
certbot/dns-cloudflare certonly \
--preferred-challenges dns --dns-cloudflare \
--dns-cloudflare-credentials /cloudflare.ini \
--dns-cloudflare-propagation-seconds 60 \
-m ${EMAIL} --agree-tos \
-d ${DOMAIN1} -d ${DOMAIN2}

自动续期证书

自动续期如果报错就用 Server酱 推送消息到微信,没报错就重载 nginx 容器。带的参数就是存放证书内容的目录,这里就是 /opt/nginx/ssl

renew_cert.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
SSLDIR="$1"
NGINXCONTAINER=nginx
SCKEY=SC1234567890

docker run --rm \
-v ${SSLDIR}:/etc/letsencrypt \
-v ${SSLDIR}/logs:/var/log/letsencrypt \
-v ${SSLDIR}/cloudflare.ini:/cloudflare.ini \
certbot/dns-cloudflare renew
CODE=$?
if [ $CODE -ne 0 ]; then
echo "Create failure!"
curl -s -o /dev/null -d "text=证书自动续期失败" https://sc.ftqq.com/${SCKEY}.send
else
docker exec ${NGINXCONTAINER} nginx -s reload
fi

添加 crontab 任务,每个月1号零点自动执行脚本。

1
0 0 1 * * /opt/nginx/ssl/renew_cert.sh /opt/nginx/ssl &

单域名证书

单域名偶尔帮朋友弄的时候会用到,因为懒得多改一次 nginx 配置,所以我签新证书都是直接 standalone,当然愿意改的也可以直接用续期的脚本来签。

签发新证书

get_cert.sh
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
#!/bin/bash
DOMAIN1=oxn.me
DOMAIN2=www.oxn.me
EMAIL=*@example.com
NGINXCONTAINER=nginx

docker ps --filter name=^/${NGINXCONTAINER}$

if [ $? -ne 0 ];then
echo "Nginx did not start"
docker run --rm -p 80:80 -p 443:443\
-v $PWD:/etc/letsencrypt \
-v $PWD/logs:/var/log/letsencrypt \
certbot/certbot certonly --standalone \
-m ${EMAIL} --agree-tos \
-d ${DOMAIN1} -d ${DOMAIN2}
else
docker stop ${NGINXCONTAINER}
docker run --rm -p 80:80 -p 443:443\
-v $PWD:/etc/letsencrypt \
-v $PWD/logs:/var/log/letsencrypt \
certbot/certbot certonly --standalone \
-m ${EMAIL} --agree-tos \
-d ${DOMAIN1} -d ${DOMAIN2}
docker start ${NGINXCONTAINER}
fi

自动续期证书

renew_cert.sh
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
#!/bin/bash
SSLDIR="$1"
NGINXCONTAINER=nginx
SCKEY=SC1234567890
DOMAIN1=oxn.me
DOMAIN2=www.oxn.me
EMAIL=*@example.com
WWW_ROOT=/usr/share/nginx/html

docker run --rm \
-v ${SSLDIR}:/etc/letsencrypt \
-v ${SSLDIR}/logs:/var/log/letsencrypt \
-v ${SSLDIR}/renew:${WWW_ROOT} \
certbot/certbot \
certonly --verbose --noninteractive --quiet --agree-tos \
--webroot -w ${WWW_ROOT} \
--email=${EMAIL} \
-d ${DOMAIN1} -d ${DOMAIN2}
CODE=$?
if [ $CODE -ne 0 ]; then
echo "Create failure!"
curl -s -o /dev/null -d "text=证书自动续期失败" https://sc.ftqq.com/${SCKEY}.send
else
docker exec ${NGINXCONTAINER} nginx -s reload
fi

nginx 需要加上相应的配置

1
2
3
4
5
6
7
8
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /opt/nginx/ssl/renew;
}

location = /.well-known/acme-challenge/ {
return 404;
}

同样,添加 crontab 任务就可以了。