certbot で更新した letsencrypt の証明書を nginx に反映する

nginx の Web サーバで letsencrypt の証明書を使うために cron で日次で下記が実行されるようにしていました。

certbot renew -t -n --agree-tos --keep

これだけだと証明書は更新されるものの nginx がリロードされないため nginx が使用している証明書が更新されませんが、どうせ logrotate のために日次でリロードされているので問題ありません。

・・・と思っていましたが、更新予定日を過ぎてからブラウザで証明書の有効期限を見てみたところ、証明書が更新されていませんでした。

certbot のログや保存されている証明書ファイルを見てみたところ証明書そのものは更新されていましたが、nginx に新しい証明書が反映されていません。

Apache の場合

Apache の場合、logorotate で graceful リロードが行われています(CentOS 7)。

cat /etc/logrotate.d/httpd | grep -A2 postrotate
# postrotate
#     /bin/systemctl reload httpd.service > /dev/null 2>/dev/null || true
# endscript

cat /usr/lib/systemd/system/httpd.service | grep ExecReload
# ExecReload=/usr/sbin/httpd $OPTIONS -k graceful

graceful リロードで証明書の更新も反映されるので、期待したとおり certbot で証明書が更新されれば翌日の logorotate で Apache にも反映されます。

なお graceful リロードは USR1 シグナルです。

Nginx の場合

Nginx の場合、logorotate で USR1 シグナルが送られています(CentOS 7)。

cat /etc/logrotate.d/nginx | grep -A2 postrotate
# postrotate
#     /bin/kill -USR1 `cat /run/nginx.pid 2>/dev/null` 2>/dev/null || true
# endscript

なお systemd の reload は HUP シグナルでした。

cat /usr/lib/systemd/system/nginx.service | grep ExecReload
# ExecReload=/bin/kill -s HUP $MAINPID

同じ USR1 ならきっと graceful リロードだろうから証明書の更新も反映されるでしょーと思ってたのですが nginx では USR1 はログが開き直されるだけのシグナルでした。

ので、証明書が更新されても logorotate の USR1 シグナルでは nginx に反映されていませんでした。

解決方法

HUP シグナル、つまり systemctl reload nginx で反映されます。

logrotate の設定を変えてしまっても良いですが、

# ...snip...
postrotate
    /bin/systemctl reload nginx
endscript

certbot の --post-hook に nginx のリロードを仕込むのが良いでしょう。

certbot renew -t -n --agree-tos --keep --post-hook='systemctl reload nginx'

なお、certbot post-hook とかでググると下記の記事がトップに出てきますが

コメントで指摘されている通り、

ディレクトリ 処理のトリガー
pre certbotが全部の証明書更新の処理を始める前に証明書の更新の有無に関わらず必ず実行されます
post certbotが全部の証明書更新の処理を終わった後で証明書の更新の有無に関わらず必ず実行されます

証明書の更新の有無に関わらず は誤りです。実際に試してみたところ pre や post も証明書が更新されたときだけ実行されています。

さいごに

Apache と同じ感覚で「USR1 シグナル打たれてるからまあ大丈夫っしょ」と思ったら全然大丈夫ではありませんでした。