スズハドットコム

IT関連や3Dプリンタの記事、たまに生活のメモを書いていきます。

Raspberry Pi + nginx + certbot でSSL その1

ラズパイで簡易なWebサーバを立ててSSL化したので記録を残します。
ハードはRaspberry Pi zero 2 W、OSはRaspberry Pi OS 11 (bullseye)、カーネル5.15です。

nginxをインストールします。

myuser@myhost:~ $ sudo apt-get install nginx

1.18.0がインストールされました。

myuser@myhost:~ $ nginx -v
nginx version: nginx/1.18.0

サービスはすでに起動していました。

myuser@myhost:~ $ sudo systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2022-12-10 16:18:55 JST; 1min 11s ago

この時点でブラウザから「http://ラズパイのホスト名orIPアドレス」にアクセスするとテストページが表示されます。

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

myuser@myhost:~ $ sudo apt-get install certbot

証明書を発行します。

myuser@myhost:~ $ sudo certbot certonly
Saving debug log to /var/log/letsencrypt/letsencrypt.log

How would you like to authenticate with the ACME CA?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: Spin up a temporary webserver (standalone)
2: Place files in webroot directory (webroot)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Plugins selected: Authenticator webroot, Installer None
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): <メールアドレスを入力>

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: <y を入力して同意する>

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: <yかnかお好みで>
Account registered.
Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c'
to cancel): <証明書発行対象のドメインを入力>
Requesting a certificate for www.example.com
Performing the following challenges:
http-01 challenge for www.example.com
Input the webroot for www.example.com: (Enter 'c' to cancel): <ドキュメントルートを入力。/var/www/html など>
Waiting for verification...
Cleaning up challenges

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

nginxのサイト設定ファイルを編集します。(念のためコピーをとってから)
「listen 443 ssl;」からの3行を追加しました。
ssl_certificateとssl_certificate_keyに指定するファイルのパスは、1個前の手順の「IMPORTANT NOTES」の中に表示されてます。

myuser@myhost:~ $ sudo cp -p  /etc/nginx/sites-available/default  /etc/nginx/sites-available/default.bak
myuser@myhost:~ $ sudo vi /etc/nginx/sites-available/default

server {
        listen 80 default_server;
        listen [::]:80 default_server;
        
        listen 443 ssl;
        ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;

nginxを再起動します。

myuser@myhost:~ $ sudo systemctl restart nginx

ブラウザから「https://ラズパイのホスト名orIPアドレス」にアクセスすると,SSLでテストページが表示されるはずです。 (証明書を作ったときのホスト名と違うと警告が出るかも)

証明書更新用のcronジョブは「/etc/cron.d/certbot」として自動で登録されていますが、systemdを使用している環境では動かないようです。
コマンドを見ると、「/usr/bin/certbotが実行可能である」かつ「/run/systemd/systemがディレクトリでない」という条件を満たすと証明書更新が実行されるようになってました。
この環境では「/run/systemd/system」というディレクトリが存在するので、証明書更新が実行されることはありません。

myuser@myhost:~ $ cat /etc/cron.d/certbot
# /etc/cron.d/certbot: crontab entries for the certbot package
#
# Upstream recommends attempting renewal twice a day
#
# Eventually, this will be an opportunity to validate certificates
# haven't been revoked, etc.  Renewal will only occur if expiration
# is within 30 days.
#
# Important Note!  This cronjob will NOT be executed if you are
# running systemd as your init system.  If you are running systemd,
# the cronjob.timer function takes precedence over this cronjob.  For
# more details, see the systemd.timer manpage, or use systemctl show
# certbot.timer.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew

なので、自分でcronジョブを設定する必要があります。
まずはコマンド単体で、--dry-runオプション付きでテストします。(これを付けず本当に証明書更新を何度も実行するとしばらくアクセス制限を喰らうとか)
--post-hookオプションは更新処理後に実行するコマンドを指定するものです。nginxのreloadを指定しました。

myuser@myhost:~ $ sudo certbot renew --dry-run --post-hook "systemctl reload nginx"
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/www.example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator webroot, Installer None
Simulating renewal of an existing certificate for www.example.com
Performing the following challenges:
http-01 challenge for www.example.com
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/www.example.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/www.example.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: systemctl reload nginx

うまくいったので、これを「/etc/cron.d/certbot」に組み込みます。
編集ポイントはこんな感じ。元の定義をコメントアウトして、さっきテストしたコマンドを入れます。
確認用に、コマンドの標準出力をログファイルに書くようにしました。 実行時刻がが16:45なのは、12時間ごとの実行タイミングを待ってられないからです。

#0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew
45 16 * * * root certbot renew --dry-run --post-hook "systemctl reload nginx" > /tmp/certbot-renew.log 2>&1

ログを確認します。
結構長いスリープが入るのね。

myuser@myhost:~ $ cat /tmp/certbot-renew.log
(略)
Non-interactive renewal: random delay of 441.47326814314255 seconds
(略)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/www.example.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: systemctl reload nginx

今度は--dry-runオプションを外します。本当に更新処理が走ります。
実行時刻がが17:10な理由は同上。

10 17 * * * root certbot renew --post-hook "systemctl reload nginx" > /tmp/certbot-renew.log 2>&1

これも成功したら、サンプルと同様に12時間ごとに実行するよう設定します。これで完了です。

0 */12 * * * root certbot renew --post-hook "systemctl reload nginx" > /tmp/certbot-renew.log 2>&1