Let’s EncryptのSSL証明書を更新する(手動とcronによる自動更新)

Let’s EncryptにSSL証明書の有効期限は3ヶ月ですので、3ヶ月に1度は取得し直す必要があります。ここでは、SSL証明書の手動での取得方法と、cronを使った自動での取得方法について説明しています。

目的

最終更新日:2021/4/19

Let’s EncryptにSSL証明書の取得の申請し、ウェブサイトをSSL化する【無料で初めてのhttps】」では、Let’s EncryptのSSL証明書を取得してサイトをSSL化し、httpsでアクセスできるようにしました。

ただ、Let’s EncryptのSSL証明書は有効期限は3ヶ月ですので、3ヶ月に1度はSSL証明書の更新する必要があります。

ですので今回は、Let’s EncryptのSSL証明書の更新をします。コマンドラインで実行する手動での更新と、cronを使った自動更新をしてみます。Let’s EncryptからSSL証明書を発行や更新の申請をするためのクライアントツールのCertbotを使っています。

環境と前提

◾️環境は以下の通りです。
ec2
amazon Linux2(CentOS7系)
Apache2.4

前提として、ec2にApacheをインストール済みである事、独自ドメインも取得済みである事、DNSのAレコードも設定済みで、「http://(独自ドメイン)」と「http://(www.独自ドメイン)」にアクセスできる状態です。

また、epel、mod_ssl、Certbotをインストールして、Let’s Encryptに最初のSSL証明書の発行申請をして取得して導入し、「https://(独自ドメイン)」と「https://(www.独自ドメイン)」にもアクセスできる状態です。
epel、mod_ssl、Certbotのインストールからサイトのssl化までは、「Let’s EncryptにSSL証明書の取得の申請し、ウェブサイトをSSL化する【無料で初めてのhttps】」を参考にしてください。

手動でSSL証明書を更新する

Let’s EncryptのSSL証明書を更新するには、certbot renewコマンドを使用しますが、まずはdry runモードでやってみます。

dry runモードでは、Let’s Encryptの本番環境ではなくステージング環境で試すことができます。本番環境は、厳しめのレート制限があるようですので、Let’s Encryptをテストする際には、まずは--dry-runオプションをつけて試すのがいいと思います。
Let'sEncryptのステージング環境

 $ sudo certbot renew --dry-run
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/(独自ドメイン).conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator standalone, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for (独自ドメイン)
http-01 challenge for www.(独自ドメイン)
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/(独自ドメイン)/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/(独自ドメイン)/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IMPORTANT NOTES:
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.$ sudo certbot renew --dry-run

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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/(独自ドメイン).conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator standalone, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for (独自ドメイン)
http-01 challenge for www.(独自ドメイン)
Cleaning up challenges
Attempting to renew cert ((独自ドメイン)) from /etc/letsencrypt/renewal/(独自ドメイン).conf produced an unexpected error: Problem binding to port 80: Could not bind to IPv4 or IPv6.. Skipping.
All renewal attempts failed. The following certs could not be renewed:
  /etc/letsencrypt/live/(独自ドメイン)/fullchain.pem (failure)
->エラー発生

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

All renewal attempts failed. The following certs could not be renewed:
  /etc/letsencrypt/live/(独自ドメイン)/fullchain.pem (failure)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 renew failure(s), 0 parse failure(s)

Problem binding to port 80: Could not bind to IPv4 or IPv6.. Skipping.というエラーが発生しました。
どうやらApacheを停止した状態で実行する必要があるそうなので、Apacheを停止して再度実行します。

 $ sudo certbot renew --dry-run
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/(独自ドメイン).conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator standalone, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for (独自ドメイン)
http-01 challenge for www.(独自ドメイン)
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/(独自ドメイン)/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/(独自ドメイン)/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Congratulations, all renewals succeeded. というメッセージが出ているので、今度はエラーなく実行できたようです。
問題なさそうなので、次は--dry-runオプションを外して本番モードで実行してみます。

$ sudo certbot renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/(独自ドメイン).conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not yet due for renewal

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

The following certs are not due for renewal yet:
  /etc/letsencrypt/live/(独自ドメイン)/fullchain.pem expires on 2020-10-28 (skipped)
No renewals were attempted.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

エラーメッセージは出ていませんが、最後の2行のメッセージを読む限り、SSL証明書の更新処理はスキップしたと言っています。

どうやら、SSL証明書の有効期限が残り30日以上ある場合、更新処理はスキップされるようです。ですので、有効期限が30日未満になれば、上のコマンドでできるはずです。

有効期限が残り30日以上あり、それでも強制的にSSL証明書を更新したい場合は、--force-renewalオプションを付ければ実行できます。

ただ、上でも書きましたが、本番環境は厳しめのレート制限があるようなので、そのレート制限にひっかかると更新処理ができなくなりますから、そこは注意した方がいいと思います。
本来、有効期限の日数に余裕がある時に更新できないのは更新する必要がないからで、負荷を軽減するためでもあると思います。それでもテストをしたい事はあると思いますから、その時はステージング環境を使う事をおすすめするという事です。ステージング環境にもレート制限はあるようですが、本番環境と比較すると緩いです。詳しくは下の公式記事を参照してみてください。
レート制限
ステージング環境

$ sudo certbot renew --force-renewal
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/(独自ドメイン).conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Plugins selected: Authenticator standalone, Installer None
Renewing an existing certificate

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/(独自ドメイン)/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/(独自ドメイン)/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

今度は、Congratulations, all renewals succeeded というメッセージが出ているので問題なく実行できたようです。
Apacheを再起動して、サイトにアクセスしSSL証明書を確認してみたら、ちゃんと有効期限が更新されていました。

ちなみにcertbot renewコマンドはApacheが起動していても実行できますが、更新された新しいSSL証明書をApacheに適用するためにはApacheの再起動かリロード(reload)が必要です。

・2020/10/16追記
certbot renewコマンドはApacheが起動していても実行できると書きましたが、再度試してみたらエラーが出てできないですね。できたのは自分の勘違いだったのか。。

エラー内容は、以下の通りです。
Attempting to renew cert (it-jog.com) from /etc/letsencrypt/renewal/it-jog.com.conf produced an unexpected error: Problem binding to port 80: Could not bind to IPv4 or IPv6.. Skipping.
All renewal attempts failed. The following certs could not be renewed:

上で書いたエラーと同じでポート80が既に使用されているからなので、Apacheを停止してからcertbot renewコマンドを実行すれば問題なくできます。

Kindle Unlimitedなら対象の本が定額で読み放題。対象本には、プログラミングなどのIT関連本、マンガ、雑誌、ビジネス書などがたくさんあります!

cronを使用して自動でSSL証明書を更新する

次は、cronを使用して自動でSSL証明書を更新する設定をします。cronについては、下のサイトがわかりやすく説明されています。
cronの日時指定を、基礎から学ぶ(分,時,日,月,曜日の指定、◯分ごと、月末起動、など)
crontabの書き方

Let’s EncryptのSSL証明書の有効期限は3ヶ月ですので、3ヶ月に1度だけSSL証明書の取得申請をすれば十分ですが、cronの登録は以下のようにしました。

5 7 2,17 * * sudo certbot renew --pre-hook "systemctl stop httpd"
毎月、2日、17日の7時5分に実行するようにしています。日時分は適当です。ただ、月2回実行するようにしています。

3ヶ月に1度でいいのですが、cronで3ヶ月に1度だけ実行するように設定すると、ちょうど有効期限の切れ目に更新処理を行うようになってしまいますし、それは月に1度でも同じですので、もし更新処理が失敗してさらに失敗した事に気づかなかったり忘れてしまったら、SSL証明書の期限が切れた状態になってしまいます。

という理由でcronで月2回実行するようにしました。これなら仮にcronによる更新処理が失敗したとしても有効期限切れまで少し余裕があります。これでcronに任せてある程度放置することができます。

有効期限が30日以上の場合はSSL証明書の取得処理はスキップされるだけなので、月2回のペースなら実行しても問題ないでしょう。このあたりのスケジューリングをどうするかの考え方は、人に寄ると思います。

また、「--pre-hook "systemctl stop httpd"」を付けています。これはcertbot renewを実行する前に、apacheを停止しています。上でも書きましたが、Apacheを起動したままcertbot renewで取得処理を行うとエラーが出るためです。
(*--pre-hookオプションは、certbot renewコマンドを実行する前に実行したいコマンドを指定できます。反対に、certbot renewコマンドを実行した後は、--post-hookオプションで指定できます。)

また、cronでSSL証明書と秘密鍵の取得をしたあとApacheを起動し直す必要がありますが、私の環境では/etc/letsencrypt/renewal/(独自ドメイン).confファイルにpost_hookでApacheをリロードする設定があり、certbot renewを実行した後にApacheのリロードをしてくれるようになっていました。

# /etc/letsencrypt/renewal/(独自ドメイン).conf
# Options used in the renewal process
[renewalparams]
authenticator = standalone
〜
post_hook = systemctl reload httpd

ただ当たり前ですが、Apacheは停止している時にreloadはできません。

$ sudo systemctl reload httpd
Job for httpd.service invalid.

ですので、/etc/letsencrypt/renewal/(独自ドメイン).confファイルのpost_hookでApacheを起動するように変更しました。

# /etc/letsencrypt/renewal/(独自ドメイン).conf
# Options used in the renewal process
[renewalparams]
authenticator = standalone
〜
#post_hook = systemctl reload httpd
post_hook = systemctl start httpd

もし、/etc/letsencrypt/renewal/(独自ドメイン).confファイルでApacheを起動する設定をしていないなら、下のようにcronのcertbot renewコマンドの--post-hookでやってあげる必要があります。

5 7 2,17 * * sudo certbot renew --pre-hook "systemctl stop httpd" --post-hook "systemctl start httpd"

cronによる設定は以上です。これで特に何もしなくても自動で定期的にLet’s EncryptのSSL証明書と秘密鍵を取得してApacheに適用できるようになりました。

cronの動作確認

cronによってSSL証明書が更新されたかどうかの動作確認ですが、まず単純にウェブサイトのSSL証明書の有効期限が更新されているかを確認すればいいでしょう。

もしサイトのSSL証明書の有効期限が更新されていなかったら、cronのログファイル/var/log/cronを確認して、cronによってcertbot renewコマンドが実行されたかどうかを確認します。

また、certbot renewコマンドの実行ログは、/var/spool/mail/(ユーザ名)ファイルに出力されるので、このファイル内のログも確認してみましょう。

あとは、取得したSSL証明書と秘密鍵は、/etc/letsencrypt/archive/(独自ドメイン)ディレクトリにありますので、ここも確認してみましょう。