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

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

※ 本ページはプロモーションが含まれています。

本ページの目的

最終更新日:2023/4/20

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を使っています。)

環境と前提

◾️サーバ環境は以下の通りです。
AWSのEC2
amazon Linux2(CentOS7系)
WebサーバはApache2.4

前提として、ec2にApacheをインストール済みである事、独自ドメインも取得済みである事、DNSのAレコードも設定済みである事。
また、Apache内にepel、mod_ssl、Certbotをインストールして、Let’s Encryptに初回のSSL証明書の発行申請をして取得して導入し、「https://(独自ドメイン)」と「https://www.(独自ドメイン)」にもアクセスできる状態である事とします。

epel、mod_ssl、Certbotのインストールと、Let’s Encryptによるサイトの初回のssl化の方法については、「Let’s EncryptにSSL証明書の取得の申請し、ウェブサイトをSSL化する【無料で初めてのhttps】」を参考にしてください。

手動でLet’s EncryptのSSL証明書を更新する

まず、手動でLet’s Encryptの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証明書を確認してみたら、ちゃんと有効期限が更新されていました。

ブラウザでSSL証明書を確認する場合ですが、Let's Encryptなら発行元の発行者名の通称や一般名(CN)の表記が"R3"、発行元の組織はLet's Encryptとなっていると思います。

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

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

エラー内容は、以下の通りです。
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:

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

(広告)アマゾンでITネットワーク関連の本を探す!

cronを使用してLet’s EncryptのSSL証明書の更新を自動化する

次は、cronを使用して自動でSSL証明書を更新する設定方法について説明します。

cronについて知らない、解らないという人は、下のサイトがわかりやすく説明されているので、初心者・初めての人でも参考になると思います。
cronの日時指定を、基礎から学ぶ(分,時,日,月,曜日の指定、◯分ごと、月末起動、など)
crontabの書き方

certbot renewコマンドで手動でSSL証明書を更新する方法を上で説明しましたが、基本的には同じコマンドをcronでスケジューリング登録して自動化するだけです。

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回実行するようにしています。

cronに登録するには、crontabコマンドの-eオプションを実行するとvimエディタが開くので編集できます。

$ crontab -e
また、現状登録されているcronの確認は、-lオプションでできます。
$ crontab -l
5 7 2,17 * * sudo certbot renew --pre-hook "systemctl stop httpd"

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

という理由で、ここではcronで月2回実行するように設定しました。これなら仮にcronによる更新処理が失敗したとしても有効期限切れまで少し余裕があります。これでcronに任せて放置する事ができます(何かイレギュラーな問題が発生しない限り)。

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

このcronの運用スケジュールで2年半以上運用していますが、特に問題は起きずに毎回ちゃんとSSL証明書を更新してくれます。完全放置でLet’s EncryptのSSL証明書の自動化ができています。数回だけSSL証明書期限切れが迫ってきたというメールが届いたけど、その後すぐにcronが走ってSSL証明書を更新してくれたので問題ないです。

■有効期限が迫ってきた時のメール
Let's Encrypt certificate expiration notice for domain "(独自ドメイン名)" (and 1 more)
Your certificate (or certificates) for the names listed below will expire in 19 days (on 2023-04-16). Please make sure to renew your certificate before then, or visitors to your web site will encounter errors.

残り19日で有効期限が期限切れになるという内容です。

また、cronで実行するcertbot renewコマンドには「--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をリロードする設定(systemctl reload httpd)がされていました。(つまり、cronでcertbot renewを実行した後にApacheのリロードが実行される。)

$ cat /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を起動するように変更しました。

$ cat /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のpost_hookでApacheを起動する設定をしないなら、cronのcertbot renewコマンドの--post-hookでやってあげても良いと思います。

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

cronによるSSL証明書の自動更新の設定方法については以上です。これで特に何もしなくても自動で定期的にLet’s EncryptのSSL証明書と秘密鍵を取得してApacheに適用してくれるはずです。あとは期待通りにしっかりcronが実行してくれるかを様子見して待ちましょう。

cron(Let’s Encrypt SSL証明書更新)の動作確認

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

もしもサイトのSSL証明書の有効期限が更新されていなかったら、サーバに入ってcronがスケジューリング通りに実行されたかを確認します。そのためには、cronのログファイル/var/log/cronの中を見て、certbot renewコマンドが実行されたかどうかを確認します。

ちゃんとcronで実行されていれば、/var/log/cronファイルにコマンドログが残っているはずです。

〜
Apr  2 07:05:01 ip-10-0-0-34 CROND[31768]: (ユーザ名) CMD (sudo certbot renew --pre-hook "systemctl stop httpd")
〜

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

■cronでcertbot renewコマンドが実行されてSSL証明書の更新処理が正常に実行された時の/var/spool/mail/(ユーザ名)のログ

From (ユーザ名)@ip-10-0-0-*.************.compute.internal  Sun Apr  2 07:09:21 2023
〜
From: "(Cron Daemon)" (ユーザ名)@ip-10-0-0-*.************.compute.internal
To: (ユーザ名)@ip-10-0-0-*.************.compute.internal
Subject: Cron <(ユーザ名)@ip-10-0-0-*> sudo certbot renew --pre-hook "systemctl stop httpd"
〜
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/(ドメイン名).conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert is due for renewal, auto-renewing...
Non-interactive renewal: random delay of 252.415742128 seconds
Plugins selected: Authenticator standalone, Installer None
Running pre-hook command: systemctl stop httpd
Renewing an existing certificate for (ドメイン名).com and www.(ドメイン名).com
Performing the following challenges:
http-01 challenge for (ドメイン名).com
http-01 challenge for www.(ドメイン名)
Waiting for verification...
Cleaning up challenges

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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all renewals succeeded:
  /etc/letsencrypt/live/(ドメイン名)/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: systemctl start httpd

■cronでcertbot renewコマンドが実行されたけど、SSL証明書の有効期限がまだ十分残っているので証明書の更新処理が実行されなかった時の/var/spool/mail/(ユーザ名)のログ

From (ユーザ名)@ip-10-0-0-*.************.compute.internal  Fri Mar 17 07:05:02 2023
〜
From: "(Cron Daemon)" (ユーザ名)@ip-10-0-0-*.************.compute.internal
To: (ユーザ名)@ip-10-0-0-*.************.compute.internal
Subject: Cron <(ユーザ名)@ip-10-0-0-*> sudo certbot renew --pre-hook "systemctl stop httpd"
〜
Saving debug log to /var/log/letsencrypt/letsencrypt.log

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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following certificates are not due for renewal yet:
  /etc/letsencrypt/live/(ドメイン名).com/fullchain.pem expires on 2023-04-16 (skipped)
No renewals were attempted.
No hooks were run.
「Cert not yet due for renewal」は証明書はまだ更新の必要がありません、「No renewals were attempted.」は更新は実行されませんでしたという意味です。 また、 (skipped)という更新処理をスキップしたワードもありますね。

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

$ sudo ls -lht /etc/letsencrypt/archive/(独自ドメイン)
合計 380K
-rw-r--r-- 1 root root 1.9K  4月  2 07:09 cert15.pem
-rw-r--r-- 1 root root 3.7K  4月  2 07:09 chain15.pem
-rw-r--r-- 1 root root 5.5K  4月  2 07:09 fullchain15.pem
-rw------- 1 root root 1.7K  4月  2 07:09 privkey15.pem
-rw-r--r-- 1 root root 1.9K  1月 17 07:05 cert14.pem
-rw-r--r-- 1 root root 3.7K  1月 17 07:05 chain14.pem
〜
privkey15.pemが秘密鍵、fullchain15.pemがSSLサーバ証明書と中間証明書が結合されたファイルだと思います。
この15という数字は15番目に更新されたSSL証明書という意味です(SSL証明書を更新する度にこの数字が1増えていく。)