Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定(2019年1月版)

isd1. はじめに

Webサイトの常時SSL化(フルHTTPS化)の流れもあり、WebサーバーにLet’s EncryptによるSSLサーバー証明書を設置する機会が多くありました。

Let’s Encryptの証明書設定については、以前もこのブログで書いたことがありますが、その後何度も設定、運用するうちに、自分の中でベストな設定方法がわかってきたので、ここでまとめておきます。

なお、以下の設定手順は、サーバーOSはCentOS 7で、2019年1月時点のものです。
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)は、ssltest2019.inaba-serverdesign.jp とします。
Let’s Encrypt側の認証サーバーから、http(s)://ssltest2019.inaba-serverdesign.jp/ としてこちらのサーバーにアクセスできるよう、DNSでAレコードを設定済みです。

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

 

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

  • 3-1. Let’s Encryptクライアントのインストール
  • 3-2. Let’s Encrypt証明書取得のためのApache/Nginxの設定
  • 3-3. Let’s Encrypt証明書の取得
  • 3-4. Apache/NginxのHTTPS設定
  • 3-5. Apache/Nginxの常時SSL設定

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

証明書を取得、更新するため、githubより、Let’s EncryptクライアントであるCertbotをインストールします。

※EPELリポジトリのcertbotパッケージをインストールしてもよいのですが、以前、僕の手元の環境で、Pythonモジュール(PyOpenSSLなど)の依存関係の問題で、証明書の自動更新がエラーとなったことがあったため、githubのものを使用するようにしています。たまたま僕の環境がおかしかったのかもしれませんが。

インストール先は /usr/local/certbot とします。

 # cd /usr/local/
 # ls

--
bin  etc  games  include  lib  lib64  libexec  sbin  share  src
--

 # git clone https://github.com/certbot/certbot

--
Cloning into 'certbot'...
remote: Enumerating objects: 34, done.
remote: Counting objects: 100% (34/34), done.
remote: Compressing objects: 100% (32/32), done.
remote: Total 61767 (delta 14), reused 3 (delta 2), pack-reused 61733
Receiving objects: 100% (61767/61767), 20.19 MiB | 6.10 MiB/s, done.
Resolving deltas: 100% (44891/44891), done.
--

 

certbot-autoコマンドが、Let’s Encryptクライアントコマンドです。

 # cd /usr/local/certbot/
 # ls -l certbot-auto

--
-rwxr-xr-x 1 root root 63562  1月 15 17:47 certbot-auto
--

 

certbot-autoコマンドを実行して、依存関係のあるCentOSソフトウェアパッケージをインストールします。

※このとき、openssl, Apache/Nginxなどが最新バージョンでないと、自動的にyum updateされるので、注意してください。

 # ./certbot-auto --debug

--
Bootstrapping dependencies for RedHat-based OSes... (you can skip this with --no-bootstrap)
yum は /bin/yum です
yum はハッシュされています (/bin/yum)
読み込んだプラグイン:fastestmirror
Loading mirror speeds from cached hostfile

...

トランザクションの要約
================================================================================
インストール  5 パッケージ (+24 個の依存関係のパッケージ)
更新          1 パッケージ (+12 個の依存関係のパッケージ)

総ダウンロード容量: 17 M
Is this ok [y/d/N]: y

...

インストール:
  augeas-libs.x86_64 0:1.4.0-6.el7         libffi-devel.x86_64 0:3.0.13-18.el7
  openssl-devel.x86_64 1:1.0.2k-16.el7     python-tools.x86_64 0:2.7.5-76.el7
  python-virtualenv.noarch 0:15.1.0-2.el7

依存性関連をインストールしました:
  dejavu-fonts-common.noarch 0:2.33-6.el7
  dejavu-sans-fonts.noarch 0:2.33-6.el7
  fontconfig.x86_64 0:2.13.0-4.3.el7
  fontpackages-filesystem.noarch 0:1.44-8.el7
  keyutils-libs-devel.x86_64 0:1.5.8-3.el7
  krb5-devel.x86_64 0:1.15.1-34.el7
  libX11.x86_64 0:1.6.5-2.el7
  libX11-common.noarch 0:1.6.5-2.el7
  libXau.x86_64 0:1.0.8-2.1.el7
  libXft.x86_64 0:2.3.2-2.el7
  libXrender.x86_64 0:0.9.10-1.el7
  libcom_err-devel.x86_64 0:1.42.9-13.el7
  libkadm5.x86_64 0:1.15.1-34.el7
  libpng.x86_64 2:1.5.13-7.el7_2
  libselinux-devel.x86_64 0:2.5-14.1.el7
  libsepol-devel.x86_64 0:2.5-10.el7
  libverto-devel.x86_64 0:0.2.5-4.el7
  libxcb.x86_64 0:1.13-1.el7
  pcre-devel.x86_64 0:8.32-17.el7
  tcl.x86_64 1:8.5.13-8.el7
  tix.x86_64 1:8.4.3-12.el7
  tk.x86_64 1:8.5.13-6.el7
  tkinter.x86_64 0:2.7.5-76.el7
  zlib-devel.x86_64 0:1.2.7-18.el7

更新:
  openssl.x86_64 1:1.0.2k-16.el7

依存性を更新しました:
  e2fsprogs.x86_64 0:1.42.9-13.el7
  e2fsprogs-libs.x86_64 0:1.42.9-13.el7
  freetype.x86_64 0:2.8-12.el7
  krb5-libs.x86_64 0:1.15.1-34.el7
  libcom_err.x86_64 0:1.42.9-13.el7
  libselinux.x86_64 0:2.5-14.1.el7
  libselinux-python.x86_64 0:2.5-14.1.el7
  libselinux-utils.x86_64 0:2.5-14.1.el7
  libsepol.x86_64 0:2.5-10.el7
  libss.x86_64 0:1.42.9-13.el7
  openssl-libs.x86_64 1:1.0.2k-16.el7
  zlib.x86_64 0:1.2.7-18.el7

完了しました!
Creating virtual environment...
Installing Python packages...
Installation succeeded.
Saving debug log to /var/log/letsencrypt/letsencrypt.log

How would you like to authenticate and install certificates?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: Apache Web Server plugin (apache)
2: Nginx Web Server plugin (nginx)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): c // (補足)ここでは証明書のインストールを行わず、また、Apache/Nginxプラグインは使用しないので、コマンドをキャンセルします。
Certbot doesn't know how to automatically configure the web server on this system. However, it can still get a certificate for you. Please run "certbot-auto certonly" to do so. You'll need to manually configure your web server to use the resulting certificate.
--

 

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

 # ./certbot-auto --version

--
certbot 0.30.0
--

 

3-2. 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/ssltest2019.inaba-serverdesign.jp)とします。

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

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

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

 

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

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

 # vi /etc/httpd/conf.d/ssltest2019.inaba-serverdesign.jp.conf

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

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

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

 

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

 # apachectl -t
 # systemctl reload httpd

 

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

 # vi /etc/nginx/conf.d/ssltest2019.inaba-serverdesign.jp.conf

--
server {
    listen       80;

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

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

 

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

 # nginx -t
 # systemctl reload nginx

 

なお、ネット上では、Nginxの設定で以下を記載する例が多くみられます。

    location = /.well-known/acme-challenge/ {
        return 404;
    }

 

これは、
「http(s)://<domain>/.well-known/acme-challenge/(末尾は / で終了)でアクセスされたときに、ステータス404(Not Found)を返す」
という意味です。
ネット上の事例にあるコメントなどを見ると、
「第三者から不正アクセスされたときに、403(Forbidden)を返すより、404(Not Found)を返したほうが、存在を隠せるので、よりセキュアになる」
という意図があるようです。

ですが、(以前のバージョンはわかりませんが、少なくとも現在のcertbotコマンドでは)/.well-known ディレクトリは、Let’s Encryptの証明書を取得・更新するときのみ一時的に作成され、すぐに削除されるので、この設定がなくて403を返すのは、ほんの10秒間ぐらいで、それ以外のほとんどの時間帯はもともと404を返します。

ですので、無駄な設定とまでは言いませんが、
「存在を知られたくないURLパスへのアクセスに対しては404を返す」
というポリシーを貫くならば、管理画面やphpMyAdminなどの管理機能のURLパスにも同様の設定を行って統一すべきでしょう。

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

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

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

証明書の取得にどれくらい時間がかかるか知りたい場合は、Linuxのtimeコマンドをつけて実行するとよいでしょう。

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

--
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for ssltest2019.inaba-serverdesign.jp
Using the webroot path /var/www/certbot/ssltest2019.inaba-serverdesign.jp for all unmatched domains.
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/ssltest2019.inaba-serverdesign.jp/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/ssltest2019.inaba-serverdesign.jp/privkey.pem
   Your cert will expire on 2019-04-08. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot-auto
   again. To non-interactively renew *all* of your certificates, run
   "certbot-auto renew"
 - 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


real    0m11.425s
user    0m4.902s
sys     0m0.163s

 

「- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/ssltest2019.inaba-serverdesign.jp/fullchain.pem
Your key file has been saved at: ~」
と出力されれば、証明書の取得は成功です。
失敗した場合は、エラーメッセージや、デバッグログ /var/log/letsencrypt/letsencrypt.log を参照して、不具合箇所を調査、解消します。

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

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

--
-rw-r--r-- 1 root root 692  1月  8 12:35 README
lrwxrwxrwx 1 root root  53  1月  8 12:35 cert.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/cert1.pem
lrwxrwxrwx 1 root root  54  1月  8 12:35 chain.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/chain1.pem
lrwxrwxrwx 1 root root  58  1月  8 12:35 fullchain.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/fullchain1.pem
lrwxrwxrwx 1 root root  56  1月  8 12:35 privkey.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/privkey1.pem
--

 

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

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

 # openssl x509 -text -noout -in /etc/letsencrypt/live/ssltest2019.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:8xx2
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
        Validity
            Not Before: Jan  8 03:35:40 2018 GMT
            Not After : Apr  8 03:35:40 2019 GMT
        Subject: CN=ssltest2019.inaba-serverdesign.jp
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
...

            X509v3 Subject Alternative Name:
                DNS:ssltest2019.inaba-serverdesign.jp
...
--

 

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

3-4. Apache/NginxのHTTPS設定

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

 # vi /etc/httpd/conf.d/ssl_ssltest2019.inaba-serverdesign.jp.conf

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

 

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

 # apachectl -t
 # systemctl restart httpd

 

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

 # vi /etc/nginx/conf.d/ssl_ssltest2019.inaba-serverdesign.jp.conf

--
server {
    listen      443 ssl http2;

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

 

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

 # nginx -t
 # systemctl restart nginx

 

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

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

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

・Apache 2.4の場合

 # vi /etc/httpd/conf.d/ssltest2019.inaba-serverdesign.jp.conf

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

 

 # vi /etc/httpd/conf.d/ssl_ssltest2019.inaba-serverdesign.jp.conf

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

 

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

 # apachectl -t
 # systemctl reload httpd

 

・Nginxの場合

 # vi /etc/nginx/conf.d/ssltest2019.inaba-serverdesign.jp.conf

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

--

 

 # vi /etc/nginx/conf.d/ssl_ssltest2019.inaba-serverdesign.jp.conf

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

 

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

 # nginx -t
 # systemctl reload nginx

 

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

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

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

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

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

毎回手作業で更新するのは手間がかかるので、自動更新する設定を行います。

証明書の更新、更新エラーが発生したときのアラートメール通知といった一連の処理をシェルスクリプトにまとめ、cronで定期的に実行するようにします。

  • 4-1. 証明書更新コマンドの確認
  • 4-2. 自動更新スクリプトの設定
  • 4-3. cronの定期実行設定

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

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

 # /usr/local/certbot/certbot-auto renew \
    --post-hook "systemctl restart 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/ssltest2019.inaba-serverdesign.jp.conf

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

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

 

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

動作確認のため、–dry-runオプションをつけて、証明書の更新をテスト実行してみます。

・Apache 2.4の場合

 # /usr/local/certbot/certbot-auto renew \
    --post-hook "systemctl restart httpd" \
    --dry-run

 

・Nginxの場合

 # /usr/local/certbot/certbot-auto renew \
    --post-hook "systemctl restart nginx" \
    --dry-run

 

以下のように、「The dry run was successful.」が出力されればOKです。

--
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for ssltest2019.inaba-serverdesign.jp
Using the webroot path /var/www/certbot/ssltest2019.inaba-serverdesign.jp for all unmatched domains.
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - The dry run was successful.
 - 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.
--

 

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

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

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

WEBSERVER_RESTART_CMD で、–post-hookに渡す、Webサーバーソフトウェアを再起動するコマンドを指定しています。
ApacheではなくNginxの場合は、httpd を nginx に置き換えてください。

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

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

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

 # vi /root/bin/update_sslcert.sh
 
--
#!/bin/bash
#
CERTBOT_CMD=/usr/local/certbot/certbot-auto
WEBSERVER_RESTART_CMD="systemctl restart httpd"

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

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

# 証明書の更新
${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オプションを追加します。

 # vi /root/bin/update_sslcert.sh

-- 変更前
...

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

-- 変更後
...

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

 

シェルスクリプトを実行します。
「Congratulations, all renewals succeeded. The following certs have been renewed:
/etc/letsencrypt/live/ssltest2019.inaba-serverdesign.jp/fullchain.pem (success)」
のように成功したメッセージが出力されればOKです。

 # /root/bin/update_sslcert.sh

--
===== Update SSL Certfile =====
2019年  1月  8日 火曜日 15:42:01 JST Update SSL Certfile start
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Non-interactive renewal: random delay of 4 seconds

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/ssltest2019.inaba-serverdesign.jp.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Plugins selected: Authenticator webroot, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for ssltest2019.inaba-serverdesign.jp
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/ssltest2019.inaba-serverdesign.jp/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

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

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/ssltest2019.inaba-serverdesign.jp/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: systemctl restart httpd
2019年  1月  8日 火曜日 15:42:28 JST Update SSL Certfile end
--

 

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

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

 # ps aux | grep httpd

--
root     22437  1.0  0.3 336488 13520 ?        Ss   15:42   0:00 /usr/sbin/httpd
apache   22439  0.0  0.1 336624  6896 ?        S    15:42   0:00 /usr/sbin/httpd
apache   22440  0.0  0.1 336624  6892 ?        S    15:42   0:00 /usr/sbin/httpd
apache   22441  0.0  0.1 336624  6892 ?        S    15:42   0:00 /usr/sbin/httpd
apache   22442  0.0  0.1 336624  6892 ?        S    15:42   0:00 /usr/sbin/httpd
apache   22443  0.0  0.1 336624  6892 ?        S    15:42   0:00 /usr/sbin/httpd
apache   22444  0.0  0.1 336624  6892 ?        S    15:42   0:00 /usr/sbin/httpd
apache   22445  0.0  0.1 336624  6892 ?        S    15:42   0:00 /usr/sbin/httpd
apache   22446  0.0  0.1 336624  6892 ?        S    15:42   0:00 /usr/sbin/httpd
root     22449  0.0  0.0 103316   880 pts/3    S+   15:42   0:00 grep httpd
--


 # less /var/log/httpd/error_log

--
[Tue Jan 08 15:42:48.349609 2019] [mpm_prefork:notice] [pid 1491] AH00170: caught SIGWINCH, shutting down gracefully
[Tue Jan 08 15:42:49.546452 2019] [auth_digest:notice] [pid 1528] AH01757: generating secret for digest authentication ...
[Tue Jan 08 15:42:49.549948 2019] [mpm_prefork:notice] [pid 1528] AH00163: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips configured -- resuming normal operations
[Tue Jan 08 15:42:49.549968 2019] [core:notice] [pid 1528] AH00094: Command line: '/usr/sbin/httpd -D FOREGROUND'
--

 

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

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

--
-rw-r--r-- 1 root root 692  1月  8 12:35 README
lrwxrwxrwx 1 root root  53  1月  8 15:42 cert.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/cert2.pem
lrwxrwxrwx 1 root root  54  1月  8 15:42 chain.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/chain2.pem
lrwxrwxrwx 1 root root  58  1月  8 15:42 fullchain.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/fullchain2.pem
lrwxrwxrwx 1 root root  56  1月  8 15:42 privkey.pem -> ../../archive/ssltest2019.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 * * 0 /root/bin/update_sslcert.sh 1>> /var/log/update_sslcert.log 2>&1
--

 

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

3分後に実施するよう設定してみます。

 # crontab -e

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

 

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

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

 # ps aux | grep httpd

--
root     22437  1.0  0.3 336488 13520 ?        Ss   15:52   0:00 /usr/sbin/httpd
apache   22439  0.0  0.1 336624  6896 ?        S    15:52   0:00 /usr/sbin/httpd
apache   22440  0.0  0.1 336624  6892 ?        S    15:52   0:00 /usr/sbin/httpd
apache   22441  0.0  0.1 336624  6892 ?        S    15:52   0:00 /usr/sbin/httpd
apache   22442  0.0  0.1 336624  6892 ?        S    15:52   0:00 /usr/sbin/httpd
apache   22443  0.0  0.1 336624  6892 ?        S    15:52   0:00 /usr/sbin/httpd
apache   22444  0.0  0.1 336624  6892 ?        S    15:52   0:00 /usr/sbin/httpd
apache   22445  0.0  0.1 336624  6892 ?        S    15:52   0:00 /usr/sbin/httpd
apache   22446  0.0  0.1 336624  6892 ?        S    15:52   0:00 /usr/sbin/httpd
root     22449  0.0  0.0 103316   880 pts/3    S+   15:52   0:00 grep httpd
--


 # less /var/log/httpd/error_log

--
[Tue Jan 08 15:52:42.689616 2019] [mpm_prefork:notice] [pid 1528] AH00170: caught SIGWINCH, shutting down gracefully
[Tue Jan 08 15:52:43.840583 2019] [auth_digest:notice] [pid 1557] AH01757: generating secret for digest authentication ...
[Tue Jan 08 15:52:43.844010 2019] [mpm_prefork:notice] [pid 1557] AH00163: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips configured -- resuming normal operations
[Tue Jan 08 15:52:43.844033 2019] [core:notice] [pid 1557] AH00094: Command line: '/usr/sbin/httpd -D FOREGROUND'
--

 

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

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

--
-rw-r--r-- 1 root root 692  1月  8 12:35 README
lrwxrwxrwx 1 root root  53  1月  8 15:52 cert.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/cert3.pem
lrwxrwxrwx 1 root root  54  1月  8 15:52 chain.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/chain3.pem
lrwxrwxrwx 1 root root  58  1月  8 15:52 fullchain.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/fullchain3.pem
lrwxrwxrwx 1 root root  56  1月  8 15:52 privkey.pem -> ../../archive/ssltest2019.inaba-serverdesign.jp/privkey3.pem
--

 

ログを確認します。

 # less /var/log/update_sslcert.log

--
===== Update SSL Certfile =====
2019年  1月  8日 火曜日 15:52:01 JST Update SSL Certfile start
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/ssltest2019.inaba-serverdesign.jp.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Plugins selected: Authenticator webroot, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for ssltest2019.inaba-serverdesign.jp
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/ssltest2019.inaba-serverdesign.jp/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

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

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/ssltest2019.inaba-serverdesign.jp/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: systemctl restart httpd
2019年  1月  8日 火曜日 15:52:24 JST Update SSL Certfile end
--

 

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

 # crontab -e

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

 

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

 # vi /root/bin/update_sslcert.sh

-- 変更前
...

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

-- 変更後
...

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

 

以上で、Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定は完了です。

isd5. Let’s Encrypt証明書の運用で気になること

Let’s Encrypt証明書の設定、自動更新の運用で気になっていることについて、いくつかコメントします。

  • 証明書有効期限の監視
  • メールアドレスの登録は必要?
  • 1サーバーで複数ドメインの証明書を運用する場合
  • サーバー証明書の削除
  • renewコマンドの–pre-hook, –post-hook, –deploy-hookオプション

証明書有効期限の監視

「いつの間にか証明書の期限が切れていて、あわてて更新する」という話はわりとよく聞きます。
Webサイトを多数運用していると、証明書の管理が大変ですよね。

「Let’s Encryptで自動更新すれば大丈夫!」と思われそうですが、Let’s Encryptでも、パッケージの依存関係や、Apache/Nginxの設定ミス、アクセス制限の設定間違い等で、更新に失敗することがあり得ます。

そういったこともあり、Let’s Encryptに限りませんが、証明書の有効期限は、監視ツールで監視すべきだと思います。
僕は、Nagios check_httpプラグインで、証明書の有効期限をチェックし、残り20日以内となったときはアラート通知するようにしています。
Zabbixや、Webサイト監視サービスでも、同様の機能がありますね。

(参考)
・NagiosでSSLサーバー証明書の有効期限を監視 – 稲葉サーバーデザイン
https://inaba-serverdesign.jp/blog/20150825/nagios_ssl_certificate_sni.html

メールアドレスの登録は必要?

証明書取得時の、Let’s Encryptへのメールアドレス登録ですが、僕は余計なメールはあまり受け取りたくないので、1ドメインのみ登録して、それ以外は登録していません。

これまでLet’s Encryptからメールで届いた情報は、
「証明書の有効期限が間近」
「Agreement(利用規約)の変更)」
「認証方式 TLS-SNI-01 が廃止されるので、現在の設定を変更するように」
といったことぐらいでしょうか。

上記の最後のは重要ですが、それ以外は、自分で証明書有効期限の監視を行っていることもあり、あまり有用な情報ではありません。
例えば「ワイルドカードの証明書に対応しました」のような、便利な機能追加や、有用な仕様変更の情報を伝えてくれるとよいのですが、そういうメールはなかったように思います。。

1サーバーで複数ドメインの証明書を運用する場合

マルチホスティングのサーバーで、1つのサーバーで全く異なるドメインの複数の証明書(example.jp と example.com と example.co.jp など)を設置して運用するケースも多いと思います。

この場合、certbot-auto renewコマンドによる更新時に、すべての証明書が更新されます。
certbot-autoコマンドが、ドメインごとのConfigファイル /etc/letsencrypt/renewal/<ドメイン>.conf を探して、すべてのドメインに対して更新してくれるんですね。

もし、Webサイトの廃止などで、このサーバーで証明書を使用しなくなるときは、Configファイルを削除しないと、certbot-auto renewコマンドが証明書の更新を試みてしまうので、注意しましょう。

証明書の削除

Webサイトの廃止、他サーバーへの移行などで、このサーバーで証明書を使用しなくなるときは、certbot-auto revokeコマンドで証明書を失効させます。
また、–delete-after-revokeオプションをつけると、証明書の失効と合わせて、証明書ファイルが削除されます。

 # /usr/local/certbot/certbot-auto revoke \
   --cert-name ssltest2019.inaba-serverdesign.jp --delete-after-revoke

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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Deleted all files relating to certificate ssltest2019.inaba-serverdesign.jp.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully revoked the certificate that was located
at /etc/letsencrypt/live/ssltest2019.inaba-serverdesign.jp/fullchain.pem

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

 

renewコマンドの–pre-hook, –post-hook, –deploy-hookオプション

renewコマンドには、–deploy-hookオプションもあります。
–pre-hook, –post-hookオプションで指定したコマンドは、証明書の更新を試みるとき(更新が成功しても失敗しても)に実行されます。
一方、–deploy-hookオプションで指定したコマンドは、証明書の更新が成功したときのみ実行されます。

ですので、「証明書の更新に失敗したときは、Apache/Nginxを再起動したくない」という場合は、–post-hookオプションの代わりに、–deploy-hookオプションを使用するとよいでしょう。

ただし、1サーバー上で複数ドメインの証明書を更新する場合、–post-hookオプションで指定したコマンドは、最後に一度だけ実行されるのに比べて、–deploy-hookオプションで指定したコマンドは、1つの証明書を更新するたびに、コマンドが実行されます。
例えば、3ドメインの証明書を運用し、–deploy-hookオプションでApacheの再起動コマンドを指定した場合、同じタイミングで3ドメインの証明書が更新されると、Apacheの再起動も3回実行されます。

ということもあり、個人的には、–post-hookオプションで十分かなと思っています。

isdおわりに

Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定について、これまでお客様のサーバー、自社で運用を重ねてきて、自分の中でベストだと思える設定方法をまとめました。

(関連記事)
・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!