Let’s Encryptの自動更新をcron化したけど地味に苦労した話

というわけで、Let's Encryptの自動更新をcron化したわけなのですが、つぶやいているとおり地味に苦労した部分があったのでその際のメモです。

なお、自分の環境は

  • CentOS 6.9
  • Let’s Encryptを導入済み
    • standaloneプラグイン利用

となっております。

はじめに

まず今までの更新は以下の手順で行っていました。

$ sudo service httpd stop
$ /path/to/certbot/certbot-auto certonly --standalone -d shimabox.net -d blog.shimabox.net
$ sudo service httpd start

certbot のパスは自身のもので読み替えてください
--webrootではなく、--standaloneを使っているので一旦apacheを止める必要があります
※ 最近サブドメのワイルドカードもいけるようになったっぽい

これを Let's Encrypt Expiry Bot から、もうそろそろ有効期限が切れますよ〜 というメールが届くたびに手動で行うのです。
最初は別になんとも思わなかったけど、さすがにもうめんどうくさいのと、今の時代決まりきっていることを手動で行うのはナンセンスすぎる。

という思考の末、自動化(cron)を決意した次第であります。

最初のつまずき

ひさびさに情報を漁ってみると、 更新には renew コマンドを使うのが正解のようです。
念のため、—dry-run と組み合わせて直接試してみます。

$ sudo /path/to/certbot/certbot-auto renew --dry-run

こんなエラーでました

Problem binding to port 80: Could not bind to IPv4 or IPv6.. Skipping.
All renewal attempts failed. The following certs could not be renewed

これは、standalone プラグインが TCP Port 80 や TCP Port 443 をListenしているため。だそうです。
つまり、一旦apche(80)を止めないとだめ。

というか手動でやっていたとき httpd stop, httpd start をやってたやないの。
落ち着け。そしてありがとう、dry-run。

pre-hook, post-hook

手動作業どおりに更新処理の前と後で httpd をゴニョるにはどうすればいいのか調べると、

を使えばいいということがわかります。
pre-hookhttpd stoppost-hookhttpd start をやればよさそうです。

$ sudo /path/to/certbot/certbot-auto renew --dry-run --pre-hook "sudo service httpd stop" --post-hook "sudo service httpd start"

成功

Congratulations, all renewals succeeded. The following certs have been renewed:

こんなメッセージが出れば成功です。
※ もちろんdry-runなので実際には更新されていません

さぁ、あとはこのコマンドをcronでの定期実行を設定すれば自動化が完成です。

cron

まずcronの設定を確認します。

起動確認

起動しているか確認します。

$ service crond status

起動

起動していなければ起動します。

$ sudo service crond start

設定

root になって作業します。

今回は、/etc/crontabに書くのではなく、/etc/cron.dにletsencrypt用として書くことにします。

# vi /etc/cron.d/letsencrypt

この時点では木曜日だったので、とりあえず 金曜日(5) 午前4:00 に実行されるように指定しておきます。
※ 実際の運用では 土曜日(6) 午前4:00 とします

0 4 * * 5 root /your/path/certbot/certbot-auto renew --pre-hook "service httpd stop" --post-hook "service httpd start"

wktkしながら次の日を待ちます。

次のつまずき

次の日、うんともすんともいっていない。。
ログ(/var/log/letsencrypt/letsencrypt.log)も確認したけど、起動すらされていない模様。これは悲しい。

こんなときはデバッグだ

こんな感じで、ログを仕込む && cronの実行時間をいじって確認してみます。

* * * * * root /your/path/certbot/certbot-auto renew --pre-hook "service httpd stop" --post-hook "service httpd start" >>/tmp/analog.log 2>>/tmp/analog-err.log

参考: Let’s Encryptをcrontabで自動化した時に実行されない時の対処法

こんなんでました

# cat /tmp/analog-err.log
Unable to find pre-hook command service in the PATH.
(PATH is /usr/bin:/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin)

わお、PATHが通っていない。。?だと??

実際の環境変数だとserviceは以下にPATHが通っています。

# which service
/sbin/service

え、なんで?どういうこと?むーわからん。

こんなときはデバッグだ!! というわけで、
cronジョブを作るのにいつものやり方でいいんですか?
を参考にcron実行時の環境変数を出力してみます。

* * * * * root env >/tmp/cron_env
# cat /tmp/cron_env
SHELL=/bin/sh
USER=root
PATH=/usr/bin:/bin
PWD=/root
LANG=ja_JP.UTF-8
SHLVL=1
HOME=/root
LOGNAME=root
_=/usr/bin/env

へぇ。/usr/bin, /bin にしか通っていないんやねぇ。。これは悲しい。

(この記事にもありますが)ちょっと調べてみると、
なんと、cronの各ジョブ実行時には,環境変数は最低限しかセットされていないとのことです。
なんと、cronの各ジョブ実行時には,環境変数は最低限しかセットされていないとのことです。

大事なことなので、2回書きました。

PATHを通す

では、上の 記事 を参考に/sbin を通してみます。

* * * * * root PATH="/sbin:$PATH" env >/tmp/cron_env

確認。

# cat /tmp/cron_env
PATH=/sbin:/usr/bin:/bin
SHELL=/bin/sh
USER=root
PWD=/root
LANG=ja_JP.UTF-8
SHLVL=1
HOME=/root
LOGNAME=root
_=/usr/bin/env

通った。

再度確認

再び実行時間をいじって実行してみます。

* * * * * root PATH="/sbin:$PATH" /your/path/certbot/certbot-auto renew --pre-hook "service httpd stop" --post-hook "service httpd start"

結果

ログ(/var/log/letsencrypt/letsencrypt.log) にも

certbot.renewal:no renewal failures

こんな感じでエラーも出ていないようだし、実際のサイトも更新出来ている。

、、おし。できた。

最終設定

最後にきちんと設定(毎週土曜の午前4:00に実行される様に)して、完了です。
くぅ、無知は辛い。

/etc/cron.d/letsencrypt

0 4 * * 6 root PATH="/sbin:$PATH" /your/path/certbot/certbot-auto renew --pre-hook "service httpd stop" --post-hook "service httpd start"

次の土曜と3ヶ月後にまた確認しよう。

おわりに

cronにはユーザの環境変数が引き継がれない ということを認識していなかったという無知が引き起こした無駄工数を体験出来ました。
しかし、こういう経験を経て人はまた強くなるのです。

参考にさせていただきました

シェアする

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

コメントする

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください