[date: 2018-07-04 15:41] [visits: 66]

Let's Encrypt证书自动更新

Let's Encrypt免费SSL证书用起来非常方便,但每次申请只有三个月有效期,在每次到期之前都需要重新申请,不过重新申请并不麻烦,只需要一行命令就完事:certbot renew

即便是一条命令就OK,也不想每隔三个月就需要登录到机器上执行更新命令,所以我在第一次申请之后就配置了crontab每隔两个月重新申请一次。

愿景是美好的,但过了一段时间后,我网站证书竟然过期了,也就是说crontab没有生效或者任务失败了,此文就是来找到这个问题所在以及修复它。

history

v1

最开始我配置了一条这样的crontab:

0 0 1 */2 * certbot renew --force-renewal

然后发现不生效,鉴于之前的经验知道crontab执行命令的路径应该是$HOME,而且不会加载环境变量,所以在这里改了一下。

v2

通过which certbot查找到命令的完整路径,然后配置到crontab中:

0 0 1 */2 * /usr/bin/certbot renew --force-renewal

但过了一段时间后发现网站的证书没有更新,自己依旧没当回事,觉得可能是month: */2这个写法不对。

v3

虽然之前在crontab文档里面是见过*/2这种写法的,但这次没去深究,遂改成1,3,5...逗号的写法:

0 0 1 1,3,5,7,9,11 * /usr/bin/certbot renew --force-renewal

结果依旧悲催。

v4

悲催后,我又改了下:

0 0 1 1,3,5,7,9,11 * /usr/bin/certbot renew --quiet --force-renewal 1>/dev/null 2>/dev/null

写这篇文章就是发生在第四次瞎B试失败后,受不了了...

now

crontab日志

为了验证,这次就不再是配置两个月间隔的crontab,就看着时间配置,比如现在是13:57,那么crontab里面就配置成:

0 14 * * * /usr/bin/certbot renew --quiet --force-renewal 1>/dev/null 2>/dev/null

到了14:00,去找crontab的日志(/var/log/cron),发现是命令是执行的,而且没有什么有效信息,日志内容如下:

Jul  4 14:00:01 ams CROND[21881]: (root) CMD (/usr/bin/certbot renew --quiet --force-renewal 1>/dev/null 2>/dev/null)
Jul  4 14:00:01 ams CROND[21882]: (root) CMD (echo "tmp" > /root/tmp)
Jul  4 14:00:01 ams CROND[21883]: (root) CMD (/usr/lib64/sa/sa1 1 1)
Jul  4 14:01:01 ams CROND[22000]: (root) CMD (run-parts /etc/cron.hourly)
Jul  4 14:01:01 ams run-parts(/etc/cron.hourly)[22000]: starting 0anacron
Jul  4 14:01:01 ams run-parts(/etc/cron.hourly)[22009]: finished 0anacron

其中echo "tmp" > /root/tmp是自己不清楚如何查看crontab日志的情况下用来验证crontab是否执行。

最终通过日志以及/root/tmp确认命令有被执行,但证书就是没有更新,由于通过手动执行命令可以更新证书,这里判断命令在crontab里面就不好使了,具体原因还不清楚。

letsencrypt日志

crontab日志中没什么发现,进入Google搜索模式几分钟后发现letsencrypt有自己的日志:/var/log/letsencrypt/letsencrypt.log,遂查看:

2018-07-04 14:00:02,500:DEBUG:certbot.main:certbot version: 0.23.0
2018-07-04 14:00:02,500:DEBUG:certbot.main:Arguments: ['--quiet', '--force-renewal']
2018-07-04 14:00:02,500:DEBUG:certbot.main:Discovered plugins: PluginsRegistry(PluginEntryPoint#manual,PluginEntryPoint#nginx,PluginEntryPoint#null,PluginEntryPoint#standalone,PluginEntryPoint#webroot)
2018-07-04 14:00:02,516:DEBUG:certbot.log:Root logging level set at 30
2018-07-04 14:00:02,516:INFO:certbot.log:Saving debug log to /var/log/letsencrypt/letsencrypt.log
2018-07-04 14:00:02,543:DEBUG:certbot.plugins.selection:Requested authenticator <certbot.cli._Default object at 0x36b5550> and installer <certbot.cli._Default object at 0x36b5550>
2018-07-04 14:00:02,546:DEBUG:certbot.renewal:Auto-renewal forced with --force-renewal...
2018-07-04 14:00:02,565:DEBUG:certbot.plugins.selection:Requested authenticator nginx and installer nginx
2018-07-04 14:00:02,570:DEBUG:certbot.plugins.disco:No installation (PluginEntryPoint#nginx): 
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/certbot/plugins/disco.py", line 126, in prepare
    self._initialized.prepare()
  File "/usr/lib/python2.7/site-packages/certbot_nginx/configurator.py", line 135, in prepare
    raise errors.NoInstallationError
NoInstallationError
2018-07-04 14:00:02,570:DEBUG:certbot.plugins.selection:No candidate plugin
2018-07-04 14:00:02,570:DEBUG:certbot.plugins.selection:No candidate plugin
2018-07-04 14:00:02,571:DEBUG:certbot.plugins.selection:Selected authenticator None and installer None
2018-07-04 14:00:02,571:INFO:certbot.main:Could not choose appropriate plugin: The nginx plugin is not working; there may be problems with your existing configuration.
The error was: NoInstallationError()
2018-07-04 14:00:02,571:WARNING:certbot.renewal:Attempting to renew cert (www.isenle.com) from /etc/letsencrypt/renewal/www.isenle.com.conf produced an unexpected error: The nginx plugin is not working; there may be problems with your existing configuration.
The error was: NoInstallationError(). Skipping.
2018-07-04 14:00:02,571:DEBUG:certbot.renewal:Traceback was:
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/certbot/renewal.py", line 422, in handle_renewal_request
    main.renew_cert(lineage_config, plugins, renewal_candidate)
  File "/usr/lib/python2.7/site-packages/certbot/main.py", line 1095, in renew_cert
    installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly")
  File "/usr/lib/python2.7/site-packages/certbot/plugins/selection.py", line 201, in choose_configurator_plugins
    diagnose_configurator_problem("authenticator", req_auth, plugins)
  File "/usr/lib/python2.7/site-packages/certbot/plugins/selection.py", line 297, in diagnose_configurator_problem
    raise errors.PluginSelectionError(msg)
PluginSelectionError: The nginx plugin is not working; there may be problems with your existing configuration.
The error was: NoInstallationError()

这堆异常大概是说没有安装nginx插件,结合自己机器上安装了nginx以及手动执行renew是OK的,所以猜测这里是因为crontab执行renew时找不到相关程序导致,而程序存在却找不到,无非就是PATH有问题,遂进行验证:

20 14 * * * echo "$PATH" > /root/tmp

到时间后查看/root/tmp发现内容是:/usr/bin:/bin,而which nginx的结果是:/usr/sbin/nginx,基本可以确定是PATH的问题,所以现在把crontab的环境变量配置一下就OK了,用crontab -e进入编辑,在最前面加上两行环境设置:

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin

0 0 1 */2 * certbot renew --quiet --force-renewal

花了半小时,问题最终得到解决,以后就不用管证书的自动续期了。

review

针对这件事情做个review,从自己v1-v4的处理中,总结出自己的一些缺点:

这篇文章不是解决问题后所写,而是当我决定来解决这个问题的时候开始写的,最开始以为是Let's Encrypt的问题,最后发现与Let's Encrypt并没有关系,只是crontab的PATH问题而已。

crontab有两种使用方式,一种是编辑/etc/crontab,一种是用crontab -e,前者默认配置了SHELL与PATH,而我在这里使用的是crontab -e,所以弄出这么个问题。