Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定(Snapを使用しない版)

isd1. はじめに

2021年3月に、
「Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定(2021年3月版)」
という記事を書きました。

この方法では、Let’s Encryptのクライアントコマンドのcertbotをインストールするために、Snap (Snappy) というソフトウェアパッケージ管理システムを使用しました。
しかし、このSnap (Snappy) 版certbotのしくみでは、以下の点が気になっていました。

  1. Snap環境全体で1~2GBほどディスクを使用する。
  2. 証明書更新タイミングを制御できない。
  3. 更新処理のログがわかりにくい。
  4. snapdが常駐することで、20MBほどメモリを使用する。

1. については、ディスクサイズが大きければ問題ないのですが、例えば、IDCFクラウドのように、ルートディスクのサイズがデフォルトで15GB固定となっていると、運用を続けるうちに、空き容量がやや不足してくるケースがありました。

2. については、証明書を更新したのち、Apache, Nginx他の、証明書を利用しているサービスに反映するため、それらのサービスを再起動するタイミングを考慮する必要があります。
(Apache, Nginxについては、証明書更新直後に再起動せずに、ログローテート時の再起動を待てばよいのですが。)

3. については、certbotコマンドが出力するメッセージがログに記録されないので、いつ、何が起こったのかがわかりにくいです。
デバッグログ /var/log/letsencrypt/letsencrypt.log があるんですが、こちらは逆に、細かすぎてわかりにくいです。

そこで、Snapを使用せずに、Python仮想環境内にpipでcertbotコマンドをインストールする方法が公式サイトに記載されていたので、ここにまとめます。

(参考)
・certbot instructions, None of the above on pip
https://certbot.eff.org/lets-encrypt/pip-other

・Amazon Linux1で [Skipping bootstrap because certbot-auto is deprecated on this system.] – Qiita
https://qiita.com/Ikumi/items/80e6ed8be0242cac245b

なお、以下の設定手順は、サーバーOSはCentOS 7, Rocky Linux 8, Amazon Linux(2ではなく、1のほう)で確認済みで、2021年10月時点ものです。

Apache, Nginxについては、Let’s Encryptに関わる設定のみ記載し、Let’s Encryptに無関係な基本的な設定は、ここでは記載しません。

コマンドはrootユーザーで実行する想定です。
必要に応じて、sudoに置き換えてください。

(参考)
・Certbotのユーザーガイド
https://certbot.eff.org/docs/using.html

・Let’s Encrypt で証明書を発行して運用するための nginx の設定 – ymyzk’s blog
https://blog.ymyzk.com/2016/02/nginx-config-for-lets-encrypt/

isd2. 要件

Let’s EncryptによるSSLサーバー証明書の取得、自動更新の要件は次のとおりとします。

  • WebサーバーソフトウェアはApacheもしくはNginxとし、事前にインストール済みとする。
  • Webサイトのダウンタイムが(ほぼ)生じないよう、更新時に、Apache, NginxなどのWebサーバーは停止せず、更新後の再起動のみとする。
  • Let’s Encryptによる証明書取得または更新の認証アクセス時、Webコンテンツディレクトリにはアクセスさせない。
  • 常時SSL化している状態で、正しく更新できる。
  • 1台のサーバーで複数の証明書を使用していても、正しく更新できるようにする。
  • ログをしっかり出力、保存する。

また、今回証明書を設置するドメイン名(FQDN)は、ssltest2021.inaba-serverdesign.jp とします。
Let’s Encrypt側の認証サーバーから、http(s)://ssltest2021.inaba-serverdesign.jp/ としてこちらのサーバーにアクセスできるよう、DNSでAレコードを設定済みです。

ssltest2021.inaba-serverdesign.jp	A	<サーバーのグローバルIPアドレス>

 

isd3. SSLサーバー証明書の取得と設定

3-1. 既存Let’s Encryptクライアントのアンインストール

もし、サーバーにLet’s Encryptクライアントcertbotをインストール済みならば、混同しないよう、アンインストールします。

※certbotをアンインストールしても、取得済みの証明書ファイルは削除されないので、ご安心ください。

EPEL版をインストールしていれば、yumコマンドで削除します。

 # yum remove certbot

 

githubから git clone コマンドでインストールした場合は、インストール先ディレクトリと /opt/eff.org ディレクトリを削除します。
以下は、インストール先が /usr/local/certbot の場合です。

 # rm -rf /usr/local/certbot/ /opt/eff.org/

 

Snap版certbotをインストールした場合は、snapdと関連パッケージを削除します。

 # rpm -qa | grep snap

--
snapd-selinux-2.49-2.el7.noarch
snappy-1.1.0-3.el7.x86_64
snapd-2.49-2.el7.x86_64
snap-confine-2.49-2.el7.x86_64
--

 # yum remove snapd snap-confine snapd-selinux

--
..
削除しました:
  snap-confine.x86_64 0:2.52-1.el7           snapd.x86_64 0:2.52-1.el7
  snapd-selinux.noarch 0:2.52-1.el7
--

 

これで、certbotコマンドや、自動更新のためのタイマー設定などが、ひととおり削除されます。

classic snap用のシンボリックリンクと、certbotコマンドのシンボリックリンクは残っているので削除します。

 # rm /snap /usr/bin/certbot

--
rm: シンボリックリンク `/snap' を削除しますか? y
rm: シンボリックリンク `/usr/bin/certbot' を削除しますか? y
--

 

3-2. python3パッケージのインストール

まずは、python3をインストールします。
augeas-libs は、Apacheプラグインで必要となるそうです。

 # yum install python3 augeas-libs

--
パッケージ augeas-libs-1.4.0-10.el7.x86_64 はインストール済みか最新バージョンで す
...
インストール:
  python3.x86_64 0:3.6.8-18.el7

依存性関連をインストールしました:
  python3-libs.x86_64 0:3.6.8-18.el7          python3-pip.noarch 0:9.0.3-8.el7
  python3-setuptools.noarch 0:39.2.0-10.el7
--

 

3-3. Let’s Encryptクライアントのインストール

certbot用のPython仮想環境を作成し、pipコマンドを最新バージョンにアップデートします。

 # python3 -m venv /opt/certbot/

 # /opt/certbot/bin/pip install --upgrade pip

--
...
Installing collected packages: pip
  Found existing installation: pip 9.0.3
    Uninstalling pip-9.0.3:
      Successfully uninstalled pip-9.0.3
Successfully installed pip-21.3
--

 

pipでcertbotをインストールします。

 # /opt/certbot/bin/pip install certbot

--
...
Installing collected packages: pycparser, cffi, urllib3, six, idna, cryptography, charset-normalizer, certifi, requests, pytz, PyOpenSSL, zope.interface, zope.hookable, zope.event, requests-toolbelt, pyrfc3339, josepy, zope.component, parsedatetime, distro, configobj, ConfigArgParse, acme, certbot
    Running setup.py install for configobj ... done
Successfully installed ConfigArgParse-1.5.3 PyOpenSSL-21.0.0 acme-1.20.0 certbot-1.20.0 certifi-2021.10.8 cffi-1.15.0 charset-normalizer-2.0.7 configobj-5.0.6 cryptography-35.0.0 distro-1.6.0 idna-3.3 josepy-1.10.0 parsedatetime-2.6 pycparser-2.20 pyrfc3339-1.1 pytz-2021.3 requests-2.26.0 requests-toolbelt-0.9.1 six-1.16.0 urllib3-1.26.7 zope.component-5.0.1 zope.event-4.5.0 zope.hookable-5.1.0 zope.interface-5.4.0
--

 

/opt/certbot/bin/certbot としてインストールされたので、使いやすいように /usr/bin/certbot としてシンボリックリンクを作成します。

 # ln -s /opt/certbot/bin/certbot /usr/bin/certbot

 

certbotコマンドでバージョンを確認します。

 # certbot --version

--
certbot 1.20.0
--

 

3-4. Let’s Encrypt証明書取得のためのApache/Nginxの設定

要件の、

  • Webサイトのダウンタイムが(ほぼ)生じないよう、更新時に、Apache, NginxなどのWebサーバーは停止せず、更新後の再起動のみとする。
  • Let’s Encryptによる証明書取得または更新の認証アクセス時、Webコンテンツディレクトリにはアクセスさせない。

に従い、認証時はLet’s Encryptの「Webrootプラグイン」を使用して、ApacheもしくはNginxを起動したまま、Let’s Encyprtの認証サーバーから認証用ファイルにアクセスさせます。
また、認証用ファイルの設置箇所は、WebコンテンツディレクトリのDocumentRootとは別のディレクトリ(ここでは /var/www/certbot/ssltest2021.inaba-serverdesign.jp)とします。

Let’s Encyprt認証サーバーからの認証アクセスURLは
http://ssltest2021.inaba-serverdesign.jp/.well-know/acme-challenge/~
となるので、URLパス /.well-know/~ は、/var/www/certbot/ssltest2021.inaba-serverdesign.jp/.well-known/~ にアクセスさせるのがポイントです。

まず、認証用ファイルを設置するディレクトリを作成します。

 # mkdir -p /var/www/certbot/ssltest2021.inaba-serverdesign.jp

 

ApacheまたはNginxのConfigで設定を追記します。

・Apache 2.4の場合
ssltest2021.inaba-serverdesign.jp のVirtualHostセクションに追記します。

 # vim /etc/httpd/conf.d/ssltest2021.inaba-serverdesign.jp.conf

--
<VirtualHost *:80>
    DocumentRoot /var/www/ssltest2021.inaba-serverdesign.jp/public_html
    ServerName ssltest2021.inaba-serverdesign.jp
...

    # for Let's Encrypt
    Alias /.well-known /var/www/certbot/ssltest2021.inaba-serverdesign.jp/.well-known

    <Directory /var/www/certbot/ssltest2021.inaba-serverdesign.jp>
        Require all granted
    </Directory>
</VirtualHost>
--

 

Apache Configファイルの文法チェックを行ったうえで、ApacheサービスをreloadしてApacheに反映します。

 # apachectl configtest
 # systemctl reload httpd

 

・Nginxの場合
ssltest2021.inaba-serverdesign.jp のserverセクションに追記します。

 # vim /etc/nginx/conf.d/ssltest2021.inaba-serverdesign.jp.conf

--
server {
    listen       80;

    server_name ssltest2021.inaba-serverdesign.jp;
    root   /var/www/ssltest2021.inaba-serverdesign.jp/public_html;
...

    # for Let's Encrypt
    location ^~ /.well-known/ {
        root /var/www/certbot/ssltest2021.inaba-serverdesign.jp;
    }
--

 

Nginx Configファイルの文法チェックを行ったうえで、NginxサービスをreloadしてNginxに反映します。

 # nginx -t
 # systemctl reload nginx

 

3-5. Let’s Encrypt証明書の取得

certbotコマンドのサブコマンド certonly で、証明書を取得します。
オプションについて補足します。

  • –webroot: Apache/Nginxを起動したままの認証とする。
  • -w: 認証時の Documentroot を指定。
  • -d: ドメインを指定。
  • –preferred-challenges: 認証方式を指定する。ここではHTTP認証とする。
  • –agree-tos: 利用規約の同意し、確認画面を表示しない。
  • -m: Let’s Encryptに登録するメールアドレス。登録したくなければ、代わりに –register-unsafely-without-email を指定すればよい。

※複数ドメインに対応した1枚の証明書を取得するときは、-w, -dを繰り返して、ドメインとDocumentRootのペアを追記します。

 # /usr/bin/certbot certonly \
    --webroot \
    -w /var/www/certbot/ssltest2021.inaba-serverdesign.jp \
    -d ssltest2021.inaba-serverdesign.jp \
    --preferred-challenges http \
    --agree-tos \
    -m <登録するメールアドレス>

--
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for ssltest2021.inaba-serverdesign.jp

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/privkey.pem
This certificate expires on 2022-01-13.
These files will be updated when the certificate renews.

NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

 

「Successfully received certificate.
 Certificate is saved at:~」
と出力されれば、証明書の取得は成功です。
失敗した場合は、エラーメッセージや、デバッグログ /var/log/letsencrypt/letsencrypt.log を参照して、不具合箇所を調査、解消します。

発行された証明書ファイル群を確認します。

 # ls -l /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/

--
-rw-r--r--. 1 root root 692 10月 15 18:50 README
lrwxrwxrwx. 1 root root  39 10月 15 18:50 cert.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp//cert1.pem
lrwxrwxrwx. 1 root root  40 10月 15 18:50 chain.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp//chain1.pem
lrwxrwxrwx. 1 root root  44 10月 15 18:50 fullchain.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp//fullchain1.pem
lrwxrwxrwx. 1 root root  42 10月 15 18:50 privkey.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp//privkey1.pem
--

 

  • 証明書: /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/cert.pem
  • 秘密鍵: /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/privkey.pem
  • 中間証明書: /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/chain.pem
  • 証明書+中間証明書: /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/fullchain.pem

証明書の内容は、opensslコマンドで確認できます。

 # openssl x509 -text -noout -in /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/cert.pem

--
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = Let's Encrypt, CN = R3
        Validity
            Not Before: Oct 15 08:50:27 2021 GMT
            Not After : Jan 13 08:50:26 2022 GMT
        Subject: CN = ssltest2021.inaba-serverdesign.jp
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
...
            X509v3 Subject Alternative Name:
                DNS:ssltest2021.inaba-serverdesign.jp
...
--

 

Not Before(発行日)、Not After(有効期限)、CN(Common Name)、DNS(Alternative Name)などを確認します。

3-6. Apache/NginxのHTTPS設定

・Apache 2.4の場合
ssltest2021.inaba-serverdesign.jp のTCP/443のVirtualHostセクション内で、証明書、秘密鍵、中間証明書として、Let’s Encryptで取得した各ファイルを指定します。

 # vim /etc/httpd/conf.d/ssl_ssltest2021.inaba-serverdesign.jp.conf

--
<VirtualHost *:443>
    DocumentRoot /var/www/ssltest2021.inaba-serverdesign.jp/public_html
    ServerName ssltest2021.inaba-serverdesign.jp
...
    <Directory /var/www/certbot/ssltest2021.inaba-serverdesign.jp>
        Require all granted
    </Directory>
...
    SSLCertificateFile /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/chain.pem
...
</VirtualHost>

 

※SSLCertificateChainFile を使用せず、SSLCertificateFile で fullchain.pem を指定してもよいです。

Apache Configファイルの文法チェックを行ったうえで、Apacheサービスを再起動してApacheに反映します。
restart ではなく reload を使用すると、処理が強制終了されず、処理終了を待ってからプロセスが再起動されます。

 # apachectl configtest
 # systemctl reload httpd

 

・Nginxの場合
ssltest2021.inaba-serverdesign.jp のTCP/443のserverセクション内で、証明書+中間証明書、秘密鍵として、Let’s Encryptで取得した各ファイルを指定します。

 # vim /etc/nginx/conf.d/ssl_ssltest2021.inaba-serverdesign.jp.conf

--
server {
    listen      443 ssl http2;

    server_name ssltest2021.inaba-serverdesign.jp;
    root   /var/www/ssltest2021.inaba-serverdesign.jp/public_html;
...
    ssl_certificate      /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/privkey.pem;
...
}
--

 

Nginx Configファイルの文法チェックを行ったうえで、Nginxサービスを再起動してNginxに反映します。
restart ではなく reload を使用すると、処理が強制終了されず、処理終了を待ってからプロセスが再起動されます。

 # nginx -t
 # systemctl reload nginx

 

・確認
Apache/Nginxの設定を反映したのち、WebブラウザでWebサイト(https://ssltest2021.inaba-serverdesign.jp/)にHTTPSでアクセスして、正しくページが表示されることを確認します。
また、Webブラウザの証明書情報を確認します。

3-7. Apache/Nginxの常時SSL設定

Webサイトを常時SSL化する場合は、HTTP用のTCP/80のセクションでリダイレクトの設定を行います。
Let’s Encryptの認証も、HTTPSにリダイレクトされて実施されるので、Let’s Encrypt認証用の設定を、HTTPS用のTCP/443のセクションに追記します。

・Apache 2.4の場合

 # vim /etc/httpd/conf.d/ssltest2021.inaba-serverdesign.jp.conf

<VirtualHost *:80>
...
    # Redirect to HTTPS
    RewriteEngine on
    RewriteRule ^/(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
...
</VirtualHost>

 

 # vim /etc/httpd/conf.d/ssl_ssltest2021.inaba-serverdesign.jp.conf

--
<VirtualHost *:443>
...
    # for Let's Encrypt
  Alias /.well-known /var/www/certbot/ssltest2021.inaba-serverdesign.jp/.well-known
...
</VirtualHost>

 

Apache Configファイルの文法チェックを行ったうえで、ApacheサービスをreloadしてApacheに反映します。

 # apachectl configtest
 # systemctl reload httpd

 

・Nginxの場合

 # vim /etc/nginx/conf.d/ssltest2021.inaba-serverdesign.jp.conf

--
server {
    listen       80;
...
    # Redirect to HTTPS
    return 301 https://$host$request_uri;
...

--

 

 # vim /etc/nginx/conf.d/ssl_ssltest2021.inaba-serverdesign.jp.conf

--
server {
    listen      443 ssl http2;
...
    # for Let's Encrypt
    location ^~ /.well-known/ {
        root /var/www/certbot/ssltest2021.inaba-serverdesign.jp;
    }
...
}
--

 

Nginx Configファイルの文法チェックを行ったうえで、NginxサービスをreloadしてNginxに反映します。

 # nginx -t
 # systemctl reload nginx

 

・確認
Apache/Nginxの設定を反映したのち、WebブラウザでWebサイト(http://ssltest2021.inaba-serverdesign.jp/)にHTTPでアクセスして、正しくHTTPSページにリダイレクトされることを確認します。

以上で、Let’s Encryptを利用したSSLサーバー証明書の取得と設定は完了です。

isd4. SSLサーバー証明書の自動更新設定

Let’s Encrypt証明書の有効期間は90日と限定されており、期限が切れる前に更新する必要があります。

※Let’s Encryptの証明書は、デフォルトでは、有効期限まで30日未満のときのみ更新可能です。有効期限まで30日以上のときは、コマンドを実行しても、証明書を更新しません。

毎回手作業で更新するのは手間がかかるので、自動更新する設定を行います。
ログや更新時刻を気にしなければ、公式サイトに記載のとおり、以下のコマンド一発で完了です。

 # echo "0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew -q" | sudo tee -a /etc/crontab > /dev/null

 

僕はもう少し細かく制御したいので、証明書の更新、更新後のApache, Nginxへの反映、更新エラーが発生したときのアラートメール通知といった一連の処理をシェルスクリプトにまとめ、cronで定期的に実行するようにします。

4-1. 証明書更新コマンドの確認

証明書の更新は、certbotコマンドのサブコマンド renew を使用します。
コマンドはこんな感じです。

 # /usr/bin/certbot renew \
    --post-hook "systemctl reload httpd"

 

オプションについて補足します。

  • –post-hook: 証明書の更新後に実行するコマンドを指定する。更新が必要なときのみ(=デフォルトでは有効期限まで30日未満のときのみ)実行される。一般的には、更新後の証明書を反映するための、Apache/Nginxを再起動するコマンドを指定する。
  • –pre-hook: 証明書の更新前に実行するコマンドを指定する。更新が必要なときのみ(=デフォルトでは有効期限まで30日未満のときのみ)実行される。
  • –dry-run: テスト実行する(いわゆるドライラン)。実際には証明書は更新しない。–pre-hook, –post-hookで指定したコマンドは実行される。このオプションはcertonlyサブコマンドでも使用できる。
  • –force-renewal: 有効期限までの日数に関わらず、強制的に証明書を更新する。ただし、一定期間内に更新できる回数には制限がある。

証明書を取得したときに比べると、全然少ないですね。
これは、certonlyサブコマンドで証明書を取得したときに、ドメインごとのConfigファイル /etc/letsencrypt/renewal/<ドメイン>.conf に、取得実行時のオプションが保存されており、renewサブコマンドによる更新実行時は、そのConfigを参照するからです。

Configファイルの内容は以下のような感じです。

 # view /etc/letsencrypt/renewal/ssltest2021.inaba-serverdesign.jp.conf

--
# renew_before_expiry = 30 days
version = 1.20.0
archive_dir = /etc/letsencrypt/archive/ssltest2021.inaba-serverdesign.jp
cert = /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/cert.pem
privkey = /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/privkey.pem
chain = /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/chain.pem
fullchain = /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/fullchain.pem

# Options used in the renewal process
[renewalparams]
account = xxx
pref_challs = http-01,
authenticator = webroot
webroot_path = /var/www/certbot/ssltest2021.inaba-serverdesign.jp,
server = https://acme-v02.api.letsencrypt.org/directory
[[webroot_map]]
--

 

認証方式や認証時の Documentroot などを変更したい場合は、このConfigファイルを修正すればよいです。

動作確認のため、–dry-runオプションをつけて、証明書の更新をテスト実行してみます。
このとき、–post-hookで指定した Apache もしくは nginx の再起動が発生しますので、ご注意ください。

※ほかのディストリビューションはわかりませんが、RHEL/CentOS系であれば、systemctl reloadであれば、Apache, Nginxの子プロセス はgraceful shutdown となり、処理が完了してから再起動されるので安全です。

・Apache 2.4の場合

 # /usr/bin/certbot renew \
    --post-hook "systemctl reload httpd" \
    --dry-run

 

・Nginxの場合

 # /usr/bin/certbot renew \
    --post-hook "systemctl reload nginx" \
    --dry-run

 

以下のように、「Congratulations, all simulated renewals succeeded:」が出力されればOKです。

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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/ssltest2021.inaba-serverdesign.jp.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Account registered.
Simulating renewal of an existing certificate for ssltest2021.inaba-serverdesign.jp

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--

 

4-2. 自動更新スクリプトの設定と動作確認

証明書の更新、更新エラーが発生したときのアラートメール通知といった一連の処理を行うシェルスクリプトを設置します。

※cronエントリーに直接certbotコマンドを設定してもよいのですが、僕はコマンドの実行時刻をログに記録したいので、たいていは、cronエントリーにコマンドをそのままセットすることはなく、シェルスクリプトを用意して設定するようにしています。

「/opt/certbot/bin/pip install –upgrade certbot」
で、certbotコマンドをアップデートします。
公式サイトでは、月次でアップデートするよう記載されていますが、アップデート実行時のログを残したいので、単独でcronエントリーを追加するのではなく、自動更新スクリプトに含めて、証明書更新の直前に実行するようにします。

cetbotコマンドによる更新実行前に、pythonで「time.sleep(random.random() * 3600)」を実行することで、0-3600秒のランダムディレイを入れています。
これは、Let’s Encryptのサーバー負荷を分散させるための措置です。
Snap版のように、1日のうちいつ実行されるかを決められないのは困りますが、1時間の範囲(ウインドウ)で制御できるのは、(僕は)許容できます。
時間範囲を狭めたいときは、3600のところを、1800(30分)や900(15分)にするとよいでしょう。

WEBSERVER_RESTART_CMD で、–post-hookに渡す、Webサーバーソフトウェアを再起動するコマンドを指定しています。
ApacheではなくNginxの場合は、httpd を nginx に置き換えてください。
週次でログローテートの設定をしているなど、他の機能で証明書の有効期限までにApacheやNginxが再起動されることがはっきりしているなら、–post-hoook による再起動は不要です。

MAILTOでは、アラートメールの通知先アドレスを指定します。
また、サーバー側ではmailコマンドでインターネット上にメールが送信できることを前提としています。

Linuxサーバーのメール送信設定については、以下などを参考にしてください。

・Postfixによるメール送信設定 – 稲葉サーバーデザイン
https://inaba-serverdesign.jp/blog/20160620/postfix_send_mail.html

 # vim /root/bin/update_sslcert.sh
 
--
#!/bin/bash
#
CERTBOT_CMD=/usr/bin/certbot
WEBSERVER_RESTART_CMD="systemctl reload httpd"

MAILTO=<メール通知先アドレス>

echo "===== Update SSL Certfile ====="
echo "`date` Update SSL Certfile start"

# certbotコマンドのアップデート
/opt/certbot/bin/pip install --upgrade certbot

# 0-3600秒のランダムディレイ
/opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)'

# 証明書の更新
${CERTBOT_CMD} renew \
  --post-hook "${WEBSERVER_RESTART_CMD}"

LE_STATUS=$?

# 証明書の取得に失敗したときはメールで通知
if [ "$LE_STATUS" != 0 ]; then
    echo "Update SSL Certfile failed" |\
    mail -s "Update SSL Certfile in `hostname`" ${MAILTO}
fi

echo "`date` Update SSL Certfile end"

# EOF
--

 

実行権限を付与します。

 # chmod 755 /root/bin/update_sslcert.sh

 

続いて、このシェルスクリプトの動作確認を行います。

シェルスクリプトを実行する前に、renewコマンドでは、証明書は有効期限まで残り30日未満にならないと更新されないため、強制的に更新するよう、–force-renewalオプションを追加します。

 # vim /root/bin/update_sslcert.sh

-- 変更前
...

${CERTBOT_CMD} renew \
  --post-hook "${WEBSERVER_RESTART_CMD}"
--

-- 変更後
...

${CERTBOT_CMD} renew \
  --post-hook "${WEBSERVER_RESTART_CMD}" --force-renewal
--

 

シェルスクリプトを実行します。

 # /root/bin/update_sslcert.sh

--
===== Update SSL Certfile =====
2021年 10月 18日 月曜日 12:21:01 JST Update SSL Certfile start
Requirement already satisfied: certbot in /opt/certbot/lib/python3.6/site-packages (1.20.0)
...
Requirement already satisfied: charset-normalizer~=2.0.0 in /opt/certbot/lib/python3.6/site-packages (from requests>=2.14.2->acme>=1.20.0->certbot) (2.0.7)
...

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/ssltest2021.inaba-serverdesign.jp.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Renewing an existing certificate for ssltest2021.inaba-serverdesign.jp

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all renewals succeeded:
  /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: systemctl reload httpd
2021年 10月 18日 月曜日 12:27:12 JST Update SSL Certfile end
--

 

上のように
「Congratulations, all renewals succeeded. The following certs have been renewed:
/etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/fullchain.pem (success)」
のように成功したメッセージが出力されればOKです。

WebブラウザでWebサイト(https://ssltest2021.inaba-serverdesign.jp/)にアクセスして、正しくページが表示されることを確認します。
また、Webブラウザの証明書情報を確認します。

Apache(もしくはNginx)プロセスが再起動されたことを、psコマンドおよびApacheのエラーログで確認します。

 # ps aux | grep httpd

--
root     18252  0.1  0.7 227340  7284 ?        Ss   11:23   0:00 /usr/sbin/http  -DFOREGROUND
apache   18253  0.0  0.3 227340  3460 ?        S    12:27   0:00 /usr/sbin/http  -DFOREGROUND
apache   18254  0.0  0.3 227340  3460 ?        S    12:27   0:00 /usr/sbin/http  -DFOREGROUND
apache   18255  0.0  0.3 227340  3460 ?        S    12:27   0:00 /usr/sbin/http  -DFOREGROUND
apache   18256  0.0  0.3 227340  3460 ?        S    12:27   0:00 /usr/sbin/http  -DFOREGROUND
apache   18257  0.0  0.3 227340  3460 ?        S    12:27   0:00 /usr/sbin/http  -DFOREGROUND
root     18270  0.0  0.0 112824   976 pts/1    R+   12:27   0:00 grep --color=auto httpd
--

 # less /var/log/httpd/error_log

--
Mon Oct 18 12:27:30.102030 2021] [mpm_event:notice] [pid 6477:tid 139638743501120] AH00493: SIGUSR1 received.  Doing graceful restart
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using fe80::436:81ff:fe1f:cfb. Set the 'ServerName' directive globally to suppress this message
[Mon Oct 18 12:27:31.169501 2021] [lbmethod_heartbeat:notice] [pid 6477:tid 139638743501120] AH02282: No slotmem from mod_heartmonitor
[Mon Oct 18 12:27:31.170666 2021] [mpm_event:notice] [pid 6477:tid 139638743501120] AH00489: Apache/2.4.37 (rocky) OpenSSL/1.1.1g configured -- resuming normal operations
[Mon Oct 18 12:27:31.170687 2021] [core:notice] [pid 6477:tid 139638743501120] AH00094: Command line: '/usr/sbin/httpd -D FOREGROUND'
--

 

証明書ファイルが更新されたことを確認します。
証明書ファイル群へのシンボリックリンク先ファイル名の世代数が、1から2に変わったことがわかります。
また、シンボリックリンクのタイムスタンプも更新実行時となっています。

 # ls -l /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/

--
-rw-r--r-- 1 root root 692  10月 15 13:40 README
lrwxrwxrwx 1 root root  57  10月 18 12:27 cert.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp/cert2.pem
lrwxrwxrwx 1 root root  58  10月 18 12:27 chain.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp/chain2.pem
lrwxrwxrwx 1 root root  62  10月 18 12:27 fullchain.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp/fullchain2.pem
lrwxrwxrwx 1 root root  60  10月 18 12:27 privkey.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp/privkey2.pem
--

 

4-3. cronの定期実行設定

証明書を自動更新するシェルスクリプトを定期実行するよう、rootユーザーのcronエントリーに追加します。
ここでは、毎週月曜の5:00に自動更新を試みるよう設定します。
標準出力、標準エラー出力を /var/log/update_sslcert.log に書き出すようにするのがポイントです。

※有効期限が残り30日以上で証明書を更新しないときなど、更新エラーではないときも標準エラー出力に出力することがあるので、標準エラー出力のログも記録したほうがよいです。

 # crontab -e

--
# Update SSL Cert File
0 5 * * mon /root/bin/update_sslcert.sh 1>> /var/log/update_sslcert.log 2>&1
--

 

シェルスクリプト単体では正しく動作するのに、cronから実行したときはうまくいかない、ということも多いので、cronに追加したエントリーは必ずテスト実行しましょう。

1時間以内に実施するよう設定してみます。

 # crontab -e

--
# Update SSL Cert File
0 5 * * mon /root/bin/update_sslcert.sh 1>> /var/log/update_sslcert.log 2>&1
0 16 * * * /root/bin/update_sslcert.sh 1>> /var/log/update_sslcert.log 2>&1
--

 

cronで設定した時刻+ランダムディレイが過ぎるのを待ち、WebブラウザでWebサイト(https://ssltest2021.inaba-serverdesign.jp/)にアクセスして、正しくページが表示されることを確認します。
また、Webブラウザの証明書情報を確認します。

Apache(もしくはNginx)プロセスが再起動されたことを、psコマンドおよびApacheのエラーログで確認します。

 # ps aux | grep httpd

--
root     18252  0.1  0.7 227340  7284 ?        Ss   11:23   0:00 /usr/sbin/http  -DFOREGROUND
apache   18253  0.0  0.3 227340  3460 ?        S    16:36   0:00 /usr/sbin/http  -DFOREGROUND
apache   18254  0.0  0.3 227340  3460 ?        S    16:36   0:00 /usr/sbin/http  -DFOREGROUND
apache   18255  0.0  0.3 227340  3460 ?        S    16:36   0:00 /usr/sbin/http  -DFOREGROUND
apache   18256  0.0  0.3 227340  3460 ?        S    16:36   0:00 /usr/sbin/http  -DFOREGROUND
apache   18257  0.0  0.3 227340  3460 ?        S    16:36   0:00 /usr/sbin/http  -DFOREGROUND
root     18270  0.0  0.0 112824   976 pts/1    R+   16:36   0:00 grep --color=auto httpd
--

 # less /var/log/httpd/error_log

--
[Mon Oct 18 16:36:31.102030 2021] [mpm_event:notice] [pid 6477:tid 139638743501120] AH00493: SIGUSR1 received.  Doing graceful restart
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using fe80::436:81ff:fe1f:cfb. Set the 'ServerName' directive globally to suppress this message
[Mon Oct 18 16:36:31.169501 2021] [lbmethod_heartbeat:notice] [pid 6477:tid 139638743501120] AH02282: No slotmem from mod_heartmonitor
[Mon Oct 18 16:36:31.170666 2021] [mpm_event:notice] [pid 6477:tid 139638743501120] AH00489: Apache/2.4.37 (rocky) OpenSSL/1.1.1g configured -- resuming normal operations
[Mon Oct 18 16:36:31.170687 2021] [core:notice] [pid 6477:tid 139638743501120] AH00094: Command line: '/usr/sbin/httpd -D FOREGROUND'
--

 

証明書ファイルが更新されたことを確認します。
証明書ファイル群へのシンボリックリンク先ファイル名の世代数が、2から3に変わったことがわかります。
また、シンボリックリンクのタイムスタンプも更新実行時となっています。

 # ls -l /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/

--
-rw-r--r-- 1 root root 692  10月 15 13:40 README
lrwxrwxrwx 1 root root  57  10月 18 16:36 cert.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp/cert3.pem
lrwxrwxrwx 1 root root  58  10月 18 16:36 chain.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp/chain3.pem
lrwxrwxrwx 1 root root  62  10月 18 16:36 fullchain.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp/fullchain3.pem
lrwxrwxrwx 1 root root  60  10月 18 16:36 privkey.pem -> ../../archive/ssltest2021.inaba-serverdesign.jp/privkey3.pem
--

 

ログを確認します。

 # less /var/log/update_sslcert.log

--
===== Update SSL Certfile =====
2021年 10月 18日 月曜日 16:30:01 JST Update SSL Certfile start
Requirement already satisfied: certbot in /opt/certbot/lib/python3.6/site-packages (1.20.0)
...
Requirement already satisfied: charset-normalizer~=2.0.0 in /opt/certbot/lib/python3.6/site-packages (from requests>=2.14.2->acme>=1.20.0->certbot) (2.0.7)
...

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/ssltest2021.inaba-serverdesign.jp.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Renewing an existing certificate for ssltest2021.inaba-serverdesign.jp

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all renewals succeeded:
  /etc/letsencrypt/live/ssltest2021.inaba-serverdesign.jp/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: systemctl reload httpd
2021年 10月 18日 月曜日 16:36:12 JST Update SSL Certfile end
--

 

動作確認が問題なければ、cronのテスト実行用エントリーを削除します。

 # crontab -e

--
# Update SSL Cert File
0 5 * * mon /root/bin/update_sslcert.sh 1>> /var/log/update_sslcert.log 2>&1
--

 

また、自動更新シェルスクリプトの強制更新オプション –force-renewal を削除します。

 # vim /root/bin/update_sslcert.sh

-- 変更前
...

${CERTBOT_CMD} renew \
  --post-hook "${WEBSERVER_RESTART_CMD}" --force-renewal
--

-- 変更後
...

${CERTBOT_CMD} renew \
  --post-hook "${WEBSERVER_RESTART_CMD}"
--

 

以上で、証明書の自動更新設定は完了です。

isd5. pip版certbotについて補足

Snapを使用せずに、pipでインストールしたcertbotについて補足します。

  • DNS認証プラグインのインストール
  • ディスク使用量について

DNS認証プラグインのインストール

Let’s Encryptの証明書を、DNSサービスと連携、自動認証して取得・更新する場合は、以下にDNSプラグインのインストール手順が記載されています。
(「wildcard」タブのほうです。)

・certbot instructions, None of the above on pip
https://certbot.eff.org/lets-encrypt/pip-other

/opt/certbot/bin/pip install certbot コマンドで certbot をインストールしたあと、以下のコマンドを実行して、プラグインをインストールします。
<PLUGIN> のところは、DNSサービスに応じて置き換えます。

 # /opt/certbot/bin/pip install certbot-dns-<PLUGIN>

 

例えば、DNSサービスとして、AWS Route53 を使用しているなら、certbot-dns-route53プラグインをインストールします。

 # /opt/certbot/bin/pip install certbot-dns-route53

 

これで、例えば以下のようにして、DNS認証で証明書を取得できます。
(コマンド実行には、Route 53へのアクセス権限が必要です。)

 # time /usr/bin/certbot certonly \
    --dns-route53 --dns-route53-propagation-seconds 30 \
    -d ssltest2021.inaba-serverdesign.jp \
    --agree-tos \
    -m <登録するメールアドレス>

 

証明書自動更新スクリプト内での、certbotコマンドのアップデート時に、プラグインも併せてアップデートするようにします。

 # vim /root/bin/update_sslcert.sh
 
--
...
# certbotコマンドのアップデート
/opt/certbot/bin/pip install --upgrade certbot certbot-dns-route53
...
--

 

ディスク使用量について

certbot用Python仮想環境のディスク使用量は、41MBでした。

 # du -hs /opt/certbot/

--
41M     /opt/certbot/
--

 

certbot-dns-route53 プラグインを含めると、104MBでした。

 # du -hs /opt/certbot/

--
104M     /opt/certbot/
--

 

Snapを使用する場合と異なり、すでにサーバーにインストール済みのパッケージも利用するので少ないのは当然ですが、それにしても、1GB以上使用するSnap版とは大きな違いがありますね。

isdおわりに

Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定について、Snapを使用しない方法をまとめました。

この方法では以下のようなメリットがあります。

  • ディスク使用量が少ない。
  • 証明書更新タイミングを制御できる。
  • 更新処理のログがわかりやすい。
  • Snapが簡単にはインストールできない Amazon Linux(1)でも設定できる。

一方で、pipによるcertbotコマンドのアップデートに失敗する可能性はゼロではありません。
といって、Let’s Encryptのサーバー側で、certbotコマンドが古いバージョンのままでは証明書取得でエラーとなる可能性もあるので、公式サイトで推奨しているとおり、アップデートは実施したほうが安全です。

公式サイトでは、アップデートに失敗した場合に関して、以下のように記載があります。

If this step leads to errors, run sudo rm -rf /opt/certbot and repeat all installation instructions.

「アップデートでエラーしたときは、/opt/certbot ディレクトリを削除して、この手順を最初からやり直してね。」
とのことです。

僕は、ディスクの空きが少ないサーバーでこの設定を行いましたが、まだ日が浅いので、少し注意して様子を見たいと思います。

その他、Let’s Encryptの運用については、
「Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定(2021年3月版)」
の「6. Let’s Encrypt証明書の運用で気になること」をご参照ください。
 

(関連記事)
・Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定(2021年3月版)
https://inaba-serverdesign.jp/blog/20190205/lets_encrypt_ssl_certificate_update.html

・Postfixによるメール送信設定
https://inaba-serverdesign.jp/blog/20160620/postfix_send_mail.html

・NagiosでSSLサーバー証明書の有効期限を監視
https://inaba-serverdesign.jp/blog/20150825/nagios_ssl_certificate_sni.html

 

Follow me!