はじめに
この記事は備忘録の意味合いが強いが、無料のサービスを最大限に利用して、家庭内にWebサーバーを構築している個人開発者が多いと想定し、その方達に向けた情報共有となる。
環境
動作環境
項目 | 内容 |
---|---|
機種 | Raspberry Pi 4 Model B Rev 1.4 |
OS | Raspberry Pi OS (64-bit) based on Debian Bookworm |
パケットフィルタ | nftables v1.0.6 (Lester Gooch #5) |
ファイアウォール | ufw 0.36.2 |
Webサービス | nginx version: nginx/1.22.1 |
certbot | certbot 2.1.0 |
ネットワーク環境
- ホームルーターで家庭内LANとWANを繋いでいる一般的な環境
- ルーターに付属のDDNS連携機能により、インターネットからドメイン名でルーターに到達可能
- ルーターに付属のポートフォワード機能により、インターネットからLAN内のラズパイに接続可能
※ラズパイのファイアウォールを設定してからルーターのポートフォワードを設定すると安全- ルーターの80ポートをラズパイの80ポートにフォワードするように設定
- ルーターの443以外の任意ポートをラズパイの443ポートにフォワードするように設定
インストールと設定
ufwのインストール
SSHの22ポートとHTTPSの443ポートの2つを許可し、それ以外はすべて拒否するように設定。
$ sudo apt update
$ sudo apt upgrade
$ sudo systemctl enable nftables
$ sudo systemctl start nftables
$ sudo apt install ufw
$ sudo systemctl enable ufw
$ sudo systemctl start ufw
$ sudo ufw allow SSH # SSH接続不可とならないように有効化する前に許可
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
$ sudo ufw allow "WWW Secure"
$ sudo ufw status
Status: active
To Action From
-- ------ ----
SSH ALLOW Anywhere
WWW Secure ALLOW Anywhere
SSH (v6) ALLOW Anywhere (v6)
WWW Secure (v6) ALLOW Anywhere (v6)
nginxのインストール
nginxをインストールし、ドメイン名を設定。
$ sudo apt install nginx
$ cd /etc/nginx/sites-available
$ sudo cp default mysite.conf
$ sudo vim mysite.conf # mysite.conf内のserver_nameにDDNS登録済みのドメイン名を設定
$ cd ../sites-enabled
$ sudo rm default
$ sudo ln -s ../sites-available/mysite.conf
$ sudo systemctl reload nginx
certbotのインストール
certbot本体とnginx用の証明書登録/更新プラグインをインストール。
$ sudo apt install certbot python3-certbot-nginx
certbotフックの作成
HTTP-01チャレンジを使用するには、Let's EncryptのサーバーがWebサーバーからHTTPの80ポートでトークンファイルを取得できる必要がある。
80ポートはここでしか使われないので、常に開けっ放しにするのではなく、証明書登録/更新時にのみ開くのが最適な運用だと考える。
そこで、certbotを実行するときに、--pre-hookオプションで指定したスクリプトで80ポートを開き、--post-hookオプションで指定したスクリプトで80ポートを閉じるようにする。
$ sudo vim /usr/local/bin/certbot_hook.sh # 以下の内容でスクリプトを作成
$ sudo chmod +x /usr/local/bin/certbot_hook.sh
#!/bin/sh
curdir=`pwd`
mydir=`dirname $0`
ufw=/usr/sbin/ufw
if [ "$1" = "pre" ]; then
cmd="allow WWW"
elif [ "$1" = "post" ]; then
cmd="delete allow WWW"
else
echo "usage: $0 <pre|post>"
exit 100
fi
cd "$mydir"
$ufw $cmd
code=$?
$ufw status
cd "$curdir"
exit $code
certbotのテスト実行
会話形式でcertbotのテストを実行する。
証明書の取得方式と、前述のmysite.confに設定した<ドメイン名>が選択肢として表示される。
$ sudo certbot certonly --dry-run --pre-hook "/usr/local/bin/certbot_hook.sh pre" --post-hook "/usr/local/bin/certbot_hook.sh post"
Saving debug log to /var/log/letsencrypt/letsencrypt.log
How would you like to authenticate with the ACME CA?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: Nginx Web Server plugin (nginx)
2: Spin up a temporary webserver (standalone)
3: Place files in webroot directory (webroot)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-3] then [enter] (press 'c' to cancel): 1
Which names would you like to activate HTTPS for?
We recommend selecting either all domains, or all domains in a VirtualHost/server block.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: <ドメイン名>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1
Hook 'pre-hook' ran with output:
Rule added
Rule added (v6)
Status: active
To Action From
-- ------ ----
SSH ALLOW Anywhere
WWW Secure ALLOW Anywhere
WWW ALLOW Anywhere
SSH (v6) ALLOW Anywhere (v6)
WWW Secure (v6) ALLOW Anywhere (v6)
WWW (v6) ALLOW Anywhere (v6)
Simulating a certificate request for <ドメイン名>
Hook 'post-hook' ran with output:
Rule deleted
Rule deleted (v6)
Status: active
To Action From
-- ------ ----
SSH ALLOW Anywhere
WWW Secure ALLOW Anywhere
SSH (v6) ALLOW Anywhere (v6)
WWW Secure (v6) ALLOW Anywhere (v6)
The dry run was successful.
certbotの本番実行
会話形式でcertbotを本番で実行する。
$ sudo cp /etc/nginx/sites-available/mysite.conf /tmp
$ sudo certbot --pre-hook "/usr/local/bin/certbot_hook.sh pre" --post-hook "/usr/local/bin/certbot_hook.sh post"
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Which names would you like to activate HTTPS for?
We recommend selecting either all domains, or all domains in a VirtualHost/server block.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: <ドメイン名>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1
Hook 'pre-hook' ran with output:
Rule added
Rule added (v6)
Status: active
To Action From
-- ------ ----
SSH ALLOW Anywhere
WWW Secure ALLOW Anywhere
WWW ALLOW Anywhere
SSH (v6) ALLOW Anywhere (v6)
WWW Secure (v6) ALLOW Anywhere (v6)
WWW (v6) ALLOW Anywhere (v6)
Requesting a certificate for <ドメイン名>
Hook 'post-hook' ran with output:
Rule deleted
Rule deleted (v6)
Status: active
To Action From
-- ------ ----
SSH ALLOW Anywhere
WWW Secure ALLOW Anywhere
SSH (v6) ALLOW Anywhere (v6)
WWW Secure (v6) ALLOW Anywhere (v6)
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/<ドメイン名>/fullchain.pem
Key is saved at: /etc/letsencrypt/live/<ドメイン名>/privkey.pem
This certificate expires on 2025-10-08.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Deploying certificate
Successfully deployed certificate for <ドメイン名> to /etc/nginx/sites-enabled/mysite.conf
Congratulations! You have successfully enabled HTTPS on https://<ドメイン名>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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実行後の確認
ブラウザでhttps://<ドメイン名>:<任意ポート>
にアクセスし、「Welcome to nginx!」ページが表示されること、証明書エラーが発生していないことを確認。
/etc/nginx/sites-enabled/mysite.confのリンク先の/etc/nginx/sites-available/mysite.confとバックアップ先の/tmp/mysite.confを比較。
デフォルトサーバー設定のスキーマがHTTPからHTTPSに変更され、HTTPでアクセスするとHTTPSにリダイレクトするように変更されたことを確認。
$ diff -u /tmp/mysite.conf /etc/nginx/sites-available/mysite.conf
--- /tmp/mysite.conf 2025-07-10 09:55:17.805316587 +0900
+++ /etc/nginx/sites-available/mysite.conf 2025-07-10 11:22:35.662002608 +0900
@@ -19,8 +19,6 @@
# Default server configuration
#
server {
- listen 80 default_server;
- listen [::]:80 default_server;
# SSL configuration
#
@@ -68,6 +66,14 @@
#location ~ /\.ht {
# deny all;
#}
+
+ listen [::]:443 ssl ipv6only=on; # managed by Certbot
+ listen 443 ssl; # managed by Certbot
+ ssl_certificate /etc/letsencrypt/live/<ドメイン名>/fullchain.pem; # managed by Certbot
+ ssl_certificate_key /etc/letsencrypt/live/<ドメイン名>/privkey.pem; # managed by Certbot
+ include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+
}
@@ -89,3 +95,18 @@
# try_files $uri $uri/ =404;
# }
#}
+
+server {
+ if ($host = <ドメイン名>) {
+ return 301 https://$host$request_uri;
+ } # managed by Certbot
+
+
+ listen 80 default_server;
+ listen [::]:80 default_server;
+
+ server_name <ドメイン名>;
+ return 404; # managed by Certbot
+
+
+}
\ No newline at end of file
mysite.confの手直し
ルーターのポートフォワード機能を使用して、443以外の任意ポートで受けたパケットをラズパイの443ポートに転送しているので、http://<ドメイン名>
にアクセスされた場合のリダイレクト先を正しく設定する必要がある。
※いずれにしても、HTTPの80ポートはファイアウォールで閉じているのであまり意味はない
$ sudo cp /etc/nginx/sites-available/mysite.conf /tmp/mysite.conf.2
$ sudo vim /etc/nginx/sites-available/mysite.conf # 以下のdiffの通りに修正
$ sudo systemctl reload nginx
$ diff -u /tmp/mysite.conf.2 /etc/nginx/sites-available/mysite.conf
--- /tmp/mysite.conf.2 2025-07-10 15:23:47.524809474 +0900
+++ /etc/nginx/sites-available/mysite.conf 2025-07-10 15:24:29.628232914 +0900
@@ -98,7 +98,7 @@
server {
if ($host = <ドメイン名>) {
- return 301 https://$host$request_uri;
+ return 301 https://$host:<任意ポート>$request_uri;
} # managed by Certbot
@@ -109,4 +109,4 @@
return 404; # managed by Certbot
-}
\ No newline at end of file
+}
certbotのテスト更新
certbotで証明書を更新するテストを実行する。
$ sudo certbot renew --dry-run --pre-hook "/usr/local/bin/certbot_hook.sh pre" --post-hook "/usr/local/bin/certbot_hook.sh post"
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/<ドメイン名>.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hook 'pre-hook' ran with output:
Skipping adding existing rule
Skipping adding existing rule (v6)
Status: active
To Action From
-- ------ ----
SSH ALLOW Anywhere
WWW Secure ALLOW Anywhere
WWW ALLOW Anywhere
SSH (v6) ALLOW Anywhere (v6)
WWW Secure (v6) ALLOW Anywhere (v6)
WWW (v6) ALLOW Anywhere (v6)
Simulating renewal of an existing certificate for <ドメイン名>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/<ドメイン名>/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hook 'post-hook' ran with output:
Rule deleted
Rule deleted (v6)
Status: active
To Action From
-- ------ ----
SSH ALLOW Anywhere
WWW Secure ALLOW Anywhere
SSH (v6) ALLOW Anywhere (v6)
WWW Secure (v6) ALLOW Anywhere (v6)
certbot.serviceの設定変更
certbot.timerサービスにより起動されるcertbot.serviceにより実行されるコマンドを変更する。
$ sudo systemctl certbot.service
$ sudo systemctl status certbot.service
○ certbot.service - Certbot
Loaded: loaded (/lib/systemd/system/certbot.service; static)
Active: inactive (dead) since Thu 2025-07-10 15:44:45 JST; 2min 27s ago
TriggeredBy: ● certbot.timer
Docs: file:///usr/share/doc/python-certbot-doc/html/index.html
https://certbot.eff.org/docs
Process: 4497 ExecStart=/usr/bin/certbot -q renew --no-random-sleep-on-rene>
Main PID: 4497 (code=exited, status=0/SUCCESS)
CPU: 1.548s
Jul 10 15:44:43 raspi4 systemd[1]: Starting certbot.service - Certbot...
Jul 10 15:44:45 raspi4 systemd[1]: certbot.service: Deactivated successfully.
Jul 10 15:44:45 raspi4 systemd[1]: Finished certbot.service - Certbot.
Jul 10 15:44:45 raspi4 systemd[1]: certbot.service: Consumed 1.548s CPU time.
$ sudo cp /lib/systemd/system/certbot.service /tmp
$ sudo vim /lib/systemd/system/certbot.service # 以下のdiffの通りに修正
$ sudo systemctl daemon-reload
$ sudo systemctl start certbot.service
$ sudo systemctl status certbot.service | cat
○ certbot.service - Certbot
Loaded: loaded (/lib/systemd/system/certbot.service; static)
Active: inactive (dead) since Thu 2025-07-10 16:02:20 JST; 43s ago
TriggeredBy: ● certbot.timer
Docs: file:///usr/share/doc/python-certbot-doc/html/index.html
https://certbot.eff.org/docs
Process: 4604 ExecStart=/usr/bin/certbot -q renew --no-random-sleep-on-renew --pre-hook /usr/local/bin/certbot_hook.sh pre --post /usr/local/bin/certbot_hook.sh post (code=exited, status=0/SUCCESS)
Main PID: 4604 (code=exited, status=0/SUCCESS)
CPU: 1.503s
Jul 10 16:02:19 raspi4 systemd[1]: Starting certbot.service - Certbot...
Jul 10 16:02:20 raspi4 systemd[1]: certbot.service: Deactivated successfully.
Jul 10 16:02:20 raspi4 systemd[1]: Finished certbot.service - Certbot.
Jul 10 16:02:20 raspi4 systemd[1]: certbot.service: Consumed 1.503s CPU time.
$ diff -u /tmp/certbot.service /lib/systemd/system/certbot.service
--- /tmp/certbot.service 2025-07-10 15:49:20.776876841 +0900
+++ /lib/systemd/system/certbot.service 2025-07-10 15:50:24.527911361 +0900
@@ -4,5 +4,5 @@
Documentation=https://certbot.eff.org/docs
[Service]
Type=oneshot
-ExecStart=/usr/bin/certbot -q renew --no-random-sleep-on-renew
+ExecStart=/usr/bin/certbot -q renew --no-random-sleep-on-renew --pre-hook "/usr/local/bin/certbot_hook.sh pre" --post "/usr/local/bin/certbot_hook.sh post"
PrivateTmp=true
おわりに
全く引っ掛からずにすんなりと家庭内のWebサーバーが構築できたところから、今回使用したcertbotは非常に優秀だと思われる。