Let’s Encrypt 免费 SSL 证书续期失败的处理

背景

服务器使用 OneinStack(Linux + Nginx+ MySQL/MongoDB+ PHP) 服务,在安装 OneinStack 的时候,选择安装了 Let’s Encrypt 免费 SSL 证书,而 Let’s Encrypt 免费 SSL 证书现在据说是 90 天有效,到期需要续期。安装 OneinStack 包会自动在 crontab 中添加自动续期的命令,讲道理,系统会自动续期,但是在 Let's Encrypt 证书还有 30 天到期之前,居然收到了官方邮件,提示说证书会过期并建议尽快续期。这说明自动续期的命令没有执行或者是执行过程中出错了。

确认问题

经过排查,定时任务命令 crontab 是有效的,那么就说明自动续期的命令出错了,那么得手动看看到底出了什么错。首先找到关于 certbot 的执行续期的命令,从 crontab 文件内容中就可以找到该命令:

crontab -l

可以看到续期的命令是:

/usr/local/python/bin/certbot renew --force-renew --renew-hook "/etc/init.d/nginx reload"

然后手动执行该命令,结果果然报错了:

Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/www.example.com.conf
-------------------------------------------------------------------------------
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for www.example.com
Waiting for verification...
Cleaning up challenges
Attempting to renew cert from /etc/letsencrypt/renewal/www.example.com.conf produced an unexpected error: Failed authorization procedure. www.example.com (http-01): urn:acme:error:unauthorized :: The client lacks sufficient authorization :: Invalid response from http://www.example.com/.well-known/acme-challenge/FeKzKrTsEN_Z-j7ixVz6q2vnorNN-ZBJTWRDFKK0k38: "<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="I". Skipping.

IMPORTANT NOTES:

The following errors were reported by the server:

Domain: www.example.com
Type: unauthorized
Detail: Invalid response from
http://www.example.com/.well-known/acme-challenge/3DDhheqPSYDx0LVwARFFx1N6J0KRd7Q3fMK8c9600W0:
"

<meta na"
To fix these errors, please make sure that your domain name was
entered correctly and the DNS A record(s) for that domain
contain(s) the right IP address.

原因分析

上 letsencrypt 官网找到一条 QA(https://community.letsencrypt.org/t/cant-certbot-renew-in-nginx/38786),官方人员给出第一个建议是检查 /etc/letsencrypt/renewal/www.example.com.conf 文件中的 webroot_map 内容,是否对应的网站根目录,如果 conf 文件中的 webroot_map 的地址不是网站的根目录,就会报错。

然后查看一下 www.example.com.conf 文件的内容:

cat /etc/letsencrypt/renewal/www.example.com.conf

发现 [[webroot_map]] 下面的地址,果然不是根目录,于是尝试修改为根目录后,再次执行 renew 续期的命令,就续期成功了:

Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/www.example.com.conf
-------------------------------------------------------------------------------
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for www.example.com
Waiting for verification...
Cleaning up challenges
Running renew-hook command: /etc/init.d/nginx reload
Output from nginx:
Reloading nginx configuration (via systemctl):  [  OK  ]

-------------------------------------------------------------------------------
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/www.example.com/fullchain.pem
-------------------------------------------------------------------------------

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/www.example.com/fullchain.pem (success)

推荐阅读更多精彩内容