さくらの VPS の Ubuntu 24.04 に Certbot (Let’s Encrypt) で証明書を発行してみた
前書き
Spresense でトレイルカメラを作ることに関連して、インターネット上にサーバーを用意する必要があります。Spresense で撮った写真を受け取る Web サーバーです。
この Web サーバー用に TLS 証明書を用意したほうが良いであろうという判断から Let’s Encrypt で証明書を発行する手順を記録しておく。
証明書を配置するのは さくらの VPS の Ubuntu 24.04 です。
ホスト名 test-spresense.gadgets-today.net の証明書を取得します。
Certbot のスタンドアロンモードと呼ばれるモードを使用して証明書を取得します。このスタンドアロンモードとは、Certbot が独自の軽量 Web サーバーを一時的に起動して証明書を取得するモードのことです。Certbot 独自の Web サーバーを使うので nginx とか Apache を用意する必要はないです。
Certbot のインストール
はじめに Certbot をインストールします。Certbot は Let’s Encrypt の証明書を発行するためのコマンドです。
$ sudo apt update [sudo] hoge のパスワード: ヒット:1 http://security.ubuntu.com/ubuntu noble-security InRelease ヒット:2 http://archive.ubuntu.com/ubuntu noble InRelease 取得:3 http://archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB] ヒット:4 http://archive.ubuntu.com/ubuntu noble-backports InRelease 取得:5 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages [475 kB] 取得:6 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 Packages [351 kB] 953 kB を 3秒 で取得しました (303 kB/s) パッケージリストを読み込んでいます... 完了 依存関係ツリーを作成しています... 完了 状態情報を読み取っています... 完了 アップグレードできるパッケージが 124 個あります。表示するには 'apt list --upgradable' を実行してください。 $ sudo apt install certbot パッケージリストを読み込んでいます... 完了 依存関係ツリーを作成しています... 完了 状態情報を読み取っています... 完了 以下の追加パッケージがインストールされます: python3-acme python3-certbot python3-configargparse python3-icu python3-josepy python3-parsedatetime python3-rfc3339 python3-tz 提案パッケージ: python-certbot-doc python3-certbot-apache python3-certbot-nginx python-acme-doc 以下のパッケージが新たにインストールされます: certbot python3-acme python3-certbot python3-configargparse python3-icu python3-josepy python3-parsedatetime python3-rfc3339 python3-tz アップグレード: 0 個、新規インストール: 9 個、削除: 0 個、保留: 124 個。 1,063 kB のアーカイブを取得する必要があります。 この操作後に追加で 5,428 kB のディスク容量が消費されます。 続行しますか? [Y/n] y 取得:1 http://archive.ubuntu.com/ubuntu noble/universe amd64 python3-josepy all 1.14.0-1 [22.1 kB] 取得:2 http://archive.ubuntu.com/ubuntu noble/main amd64 python3-tz all 2024.1-2 [31.4 kB] 取得:3 http://archive.ubuntu.com/ubuntu noble/universe amd64 python3-rfc3339 all 1.1-4 [6,744 B] 取得:4 http://archive.ubuntu.com/ubuntu noble/universe amd64 python3-acme all 2.9.0-1 [48.5 kB] 取得:5 http://archive.ubuntu.com/ubuntu noble/universe amd64 python3-configargparse all 1.7-1 [31.7 kB] 取得:6 http://archive.ubuntu.com/ubuntu noble/universe amd64 python3-parsedatetime all 2.6-3 [32.8 kB] 取得:7 http://archive.ubuntu.com/ubuntu noble/universe amd64 python3-certbot all 2.9.0-1 [267 kB] 取得:8 http://archive.ubuntu.com/ubuntu noble/main amd64 python3-icu amd64 2.12-1build2 [534 kB] 取得:9 http://archive.ubuntu.com/ubuntu noble/universe amd64 certbot all 2.9.0-1 [89.2 kB] 1,063 kB を 2秒 で取得しました (604 kB/s) パッケージを事前設定しています ... 以前に未選択のパッケージ python3-josepy を選択しています。 (データベースを読み込んでいます ... 現在 124969 個のファイルとディレクトリがインストールされています。) .../0-python3-josepy_1.14.0-1_all.deb を展開する準備をしています ... python3-josepy (1.14.0-1) を展開しています... 以前に未選択のパッケージ python3-tz を選択しています。 .../1-python3-tz_2024.1-2_all.deb を展開する準備をしています ... python3-tz (2024.1-2) を展開しています... 以前に未選択のパッケージ python3-rfc3339 を選択しています。 .../2-python3-rfc3339_1.1-4_all.deb を展開する準備をしています ... python3-rfc3339 (1.1-4) を展開しています... 以前に未選択のパッケージ python3-acme を選択しています。 .../3-python3-acme_2.9.0-1_all.deb を展開する準備をしています ... python3-acme (2.9.0-1) を展開しています... 以前に未選択のパッケージ python3-configargparse を選択しています。 .../4-python3-configargparse_1.7-1_all.deb を展開する準備をしています ... python3-configargparse (1.7-1) を展開しています... 以前に未選択のパッケージ python3-parsedatetime を選択しています。 .../5-python3-parsedatetime_2.6-3_all.deb を展開する準備をしています ... python3-parsedatetime (2.6-3) を展開しています... 以前に未選択のパッケージ python3-certbot を選択しています。 .../6-python3-certbot_2.9.0-1_all.deb を展開する準備をしています ... python3-certbot (2.9.0-1) を展開しています... 以前に未選択のパッケージ python3-icu を選択しています。 .../7-python3-icu_2.12-1build2_amd64.deb を展開する準備をしています ... python3-icu (2.12-1build2) を展開しています... 以前に未選択のパッケージ certbot を選択しています。 .../8-certbot_2.9.0-1_all.deb を展開する準備をしています ... certbot (2.9.0-1) を展開しています... python3-configargparse (1.7-1) を設定しています ... python3-parsedatetime (2.6-3) を設定しています ... python3-icu (2.12-1build2) を設定しています ... python3-tz (2024.1-2) を設定しています ... python3-josepy (1.14.0-1) を設定しています ... python3-rfc3339 (1.1-4) を設定しています ... python3-acme (2.9.0-1) を設定しています ... python3-certbot (2.9.0-1) を設定しています ... certbot (2.9.0-1) を設定しています ... Created symlink /etc/systemd/system/timers.target.wants/certbot.timer → /usr/lib/systemd/system/certbot.timer. man-db (2.12.0-4build2) のトリガを処理しています ... Scanning processes... Scanning candidates... Scanning linux images... Pending kernel upgrade! Running kernel version: 6.8.0-35-generic Diagnostics: The currently running kernel version is not the expected kernel version 6.8.0-41-generic. Restarting the system to load the new kernel will not be handled automatically, so you should consider rebooting. Restarting services... Service restarts being deferred: /etc/needrestart/restart.d/dbus.service systemctl restart getty@tty1.service systemctl restart serial-getty@ttyS0.service systemctl restart systemd-logind.service systemctl restart unattended-upgrades.service No containers need to be restarted. User sessions running outdated binaries: nobi @ session #1: java[935] nobi @ session #2410: java[281638] nobi @ user manager service: systemd[732] No VM guests are running outdated hypervisor (qemu) binaries on this host. $ which certbot /usr/bin/certbot $
Certbot のスタンドアロンモードで使用する Web サーバーは tcp 80 番ポートを使用します。このため、すでに tcp 80 番ポートが使用されて「いない」ことを確認する。
ss コマンドで確認します。
$ sudo ss -tuln | grep :80
$
上記 ss コマンドを実行してその結果何も表示されなかったことから tcp 80 番ポートが使用されて「いない」ことが確認できました。
パケットフィルターの設定で tcp 80 番ポート宛の通信を許可する
次に、さくらの VPS に標準で具備されているパケットフィルターの設定で tcp 80 番ポートを許可します。すでに許可している環境ではこのステップは不要です。
大雑把な手順だけ書いておきます。
さくらの VPS にログインしてパケットフィルターを設定する VPS サーバーをクリックしたあと、“パケットフィルターを設定” をクリックします。
次に、
- フィルターの種類:カスタム
- プロトコル:TCP
- ポート番号:80
- 許可する送信元 IP アドレス:すべて許可
を選択して、“設定を保存する” をクリックします。
もし、サーバー OS (今回の場合 Ubuntu 24.04) 上のファイアウォール機能 (ufw) を使っている場合は、そのファイアウォールにおいても tcp 80 を許可する必要があります。
スタンドアロンモードで証明書を発行する
certbot コマンドを以下のように実行して証明書を発行します。
$ sudo certbot certonly --standalone -d test-spresense.gadgets-today.net [sudo] hoge のパスワード: Saving debug log to /var/log/letsencrypt/letsencrypt.log Requesting a certificate for test-spresense.gadgets-today.net Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/test-spresense.gadgets-today.net/fullchain.pem Key is saved at: /etc/letsencrypt/live/test-spresense.gadgets-today.net/privkey.pem This certificate expires on 2024-12-06. These files will be updated when the certificate renews. Certbot has set up a scheduled task to automatically renew this certificate in the background. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $
エラーなく証明書が発行され、Certbot がその証明書を Let’s Encrypt 側からダウンロードして /etc/letsencrypt/live/test-spresense.gadgets-today.net ディレクトリに保存したことがわかります。
$ sudo ls -ltr /etc/letsencrypt/live/test-spresense.gadgets-today.net
合計 4
lrwxrwxrwx 1 root root 54 9月 7 14:50 privkey.pem -> ../../archive/test-spresense.gadgets-today.net/privkey1.pem
lrwxrwxrwx 1 root root 51 9月 7 14:50 cert.pem -> ../../archive/test-spresense.gadgets-today.net/cert1.pem
lrwxrwxrwx 1 root root 56 9月 7 14:50 fullchain.pem -> ../../archive/test-spresense.gadgets-today.net/fullchain1.pem
lrwxrwxrwx 1 root root 52 9月 7 14:50 chain.pem -> ../../archive/test-spresense.gadgets-today.net/chain1.pem
-rw-r--r-- 1 root root 692 9月 7 14:50 README
$
スタンドアロンモードでやっていること
certbot コマンドを実行したあと、以下のような動作が実行されます。
- certbot が Web サーバーを起動して Let’s Encrypt 側に証明書発行リクエストを送ります
- そのリクエストを受けて Let’s Encrypt が certbot にトークン (ランダムな文字列) を送信します
- certbot がトークン + α を特定のディレクトリに保存します
- Let’s Encrypt が certbot が起動した Web サーバーにアクセスし、certbot が保存したトークン + α を確認します
- Let’s Encrypt が問題ないと判断できたら証明書を発行します
- certbot が発行された証明書を取得します
- certbot が Web サーバーを停止します
注意が必要なのが上記の赤文字のところです。
Let’s Encrypt が、certbot が起動した Web サーバーにインターネット側からアクセスしてきます。
アクセスするときにホスト名 (今回の場合 test-spresense.gadgets-today.net ) を使って通信してきますので、ホスト名の名前解決結果が certbot コマンドが動作しているサーバーの IP アドレスに解決される必要があります。名前解決が正しく行えない場合は証明書が発行されません。
certbot コマンドが動作しているサーバーの IP アドレスに名前解決されたということは、証明書をリクエストしたユーザーが、該当のドメイン名 (今回の場合 gadgets-today.net) を保有していると判断することができます。
この結果を受けて Let’s Encrypt はリクエストしたユーザーが正式な gadgets-today.net ドメインの所有者と捉えて証明書を発行してくれるわけです。
証明書の自動更新について
Let’s Encrypt の証明書が無事発行されたら、次に気になるのが証明書が自動更新されるのかどうかですね。
Let’s Encyrpt の証明書の有効期限は 3 ヶ月 (90 日かな) だから、証明書を作ったこのタイミングで自動更新の設定もやっておきたいところ。
最近の Certbot はかなりイケているようで、僕の場合は証明書を発行したことで (?) 自動更新の設定もできあがっていました。
証明書が自動更新されることの確認方法
sudo certbot renew –dry-run コマンドで自動更新できるかどうかがシミュレートできます。だからひとことで言うと、このコマンドが失敗しなければオッケーということになる。
さっそくやってみた。
$ sudo certbot renew --dry-run Saving debug log to /var/log/letsencrypt/letsencrypt.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Processing /etc/letsencrypt/renewal/test-spresense.gadgets-today.net.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Account registered. Simulating renewal of an existing certificate for test-spresense.gadgets-today.net - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulations, all simulated renewals succeeded: /etc/letsencrypt/live/test-spresense.gadgets-today.net/fullchain.pem (success) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $
上記のように Congratulations うんぬんの表示が出れば良いようだ。
/etc/letsencrypt/live/test-spresense.gadgets-today.net/fullchain.pem が証明書のファイル名であるため、今回発行した証明書 = test-spresense.gadgets-today.net の証明書が自動更新される設定になっていることがわかるわけだ。
証明書が Certbot の自動更新対象になっているかどうかを確認したい場合は以下のコマンドを使う。
$ sudo certbot certificates Saving debug log to /var/log/letsencrypt/letsencrypt.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Found the following certs: Certificate Name: test-spresense.gadgets-today.net Serial Number: 309f1fd36bef5fe9148b6ab2a2532091973 Key Type: ECDSA Domains: test-spresense.gadgets-today.net Expiry Date: 2024-12-06 04:52:02+00:00 (VALID: 89 days) Certificate Path: /etc/letsencrypt/live/test-spresense.gadgets-today.net/fullchain.pem Private Key Path: /etc/letsencrypt/live/test-spresense.gadgets-today.net/privkey.pem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $
Domains のところで確認できる。複数の証明書が自動更新対象になっていたらココに複数表示されるんだと思うよ。
Expiry Date のところで、証明書の期限が切れる日時が確認できる。上記の例では 2024/12/05 だ。
Ubuntu の場合 (?) は、crontab ではなく systemd タイマーの機能を使って自動更新の処理が自動的に実行されているようだ。この点を確認するには以下のコマンドを実行する。
$ systemctl status certbot.timer ● certbot.timer - Run certbot twice daily Loaded: loaded (/usr/lib/systemd/system/certbot.timer; enabled; preset: enabled) Active: active (waiting) since Sat 2024-09-07 13:38:34 JST; 2h 9min ago Trigger: Sun 2024-09-08 03:21:25 JST; 11h left Triggers: ● certbot.service $
Active: active (waiting) になっていることからタイマーが有効になっていて、自動更新の処理が動くことが確認できます。
Trigger のところが次回自動更新の処理が動くタイミングだ。
Run certbot twice daily と表示されていることから、1 日 2 回自動更新の処理が動くようだ。
しかし、実際に証明書が更新されるのは期限が切れるまでの日数が 30 日を切った後ってことのようだから焦らないようにしよう。このことは conf ファイルを見ることで確認できる。
$ sudo cat /etc/letsencrypt/renewal/test-spresense.gadgets-today.net.conf # renew_before_expiry = 30 days version = 2.9.0 archive_dir = /etc/letsencrypt/archive/test-spresense.gadgets-today.net cert = /etc/letsencrypt/live/test-spresense.gadgets-today.net/cert.pem privkey = /etc/letsencrypt/live/test-spresense.gadgets-today.net/privkey.pem chain = /etc/letsencrypt/live/test-spresense.gadgets-today.net/chain.pem fullchain = /etc/letsencrypt/live/test-spresense.gadgets-today.net/fullchain.pem # Options used in the renewal process [renewalparams] account = ef7e450b36e1069f1970720c8f235718 authenticator = standalone server = https://acme-v02.api.letsencrypt.org/directory key_type = ecdsa $
# から始まるコメントとして書かれているが、証明書の有効期限が残り 30 日を切ったら更新すると思われる記載がある。
有効期限が 2024/12/05 だから、遅くても 12/06 くらいに更新されていることが確認できれば良いわけだ。グーグルカレンダーに登録してその日になったらチェックしてみようと思う。
あと、authenticator のところに standalone と表示されている。これは Certbot がスタンドアロンモードで証明書を更新することを示している。