能書き
私的サーバー構築日誌:仕切り直しからの自宅サーバー構築の続きです。
表題通りです。前回は nginx on docker でリバースプロキシを立ててみましたが、この環境下で Let's Encrypt の証明書を取得し、維持する手順を模索します。
調べてみましたが、似たような内容の記事は案外少なかったですね。特に維持の手順。私も手順を考えてみましたが、どうもDockerとは馴染みにくい方法しか思い付きませんでした。そして試行錯誤の挙句、ホストマシンのcronとの組合せになりました。
目標
- Dockerコンテナで立てたnginxリバースプロキシを前提とする
- リバースプロキシに証明書を持たせて、上流(upstream)にはHTTPで接続。
- 証明書を Let's Encrypt で取得(certbotを利用)
- 証明書を定期的に自動更新する。
概要
証明書の取得と維持の為に、リバースプロキシのDockerコンテナにcertbot
をインストールしてしまいます。後は標準的な手順通り。
本当は、褒められた方法ではない事を承知で、Dockerコンテナにcron
を入れたかったのですが。実際にやってみると、リバースプロキシが動かなくなります。困ったものです。
そういう訳で、コマンドの定期起動は、ホストマシンのcron
設定で実現する事にします。
こういう事をすると後で忘れるので避けたかったんですけどね。リバースプロキシだけでLXDコンテナにするのも大袈裟に過ぎる気がしますし。残念です。
参考文献
- nginxによるバーチャルホスト/リバースプロキシによる Let's Encrypt 証明書の取得方法など - Qiita
- SSL証明書の自動更新。 - 無料のSSL証明書Let’s Encryptを設定・更新・自動更新する方法(CentOS7, Apache2.4対応) - プログラミング入門ナビ by Proglus(プログラス)
- 私的サーバー構築日誌:nginx on docker でリバースプロキシ - Qiita
- 私的サーバー構築日誌:LAN内DNSサーバー Unbound - Qiita … 我が家の家庭内DNSです。バーチャルホストを動かす為に、少し追記が必要になります。
-
【cron】改めてcronの設定方法について勉強し直してみた - とーますメモ …
/etc/cron.d/
に置く設定ファイルは、拡張子を付けてはいけないようです。 - Docker Composeでログの設定をする - Qiita
Dockerコンテナでcron
を使う方法は、今回はやめましたが。参考文献だけ一応挙げておきます。
- Docker コンテナで cron を使う - Docker コンテナ内でタスクを cron 起動する - Qiita
- Dockerコンテナ内でcronを実行し実行ログを出力する方法 - ゲンゾウ用ポストイット
- Dockerコンテナの中身のタイムゾーンを日本時刻にする方法 - My CMS
実験環境を構築
前回と同じ実験環境を構築します。リバースプロキシにおいて若干の手順変更がある為、改めて全手順を掲載します。
インターネット設定
外のインターネットから、我が家のサーバーにアクセスできるように設定します。まずはDNS、それから家庭用ルーターのポートフォワーディングですね。80番ポートと443番ポートです。これらは環境によって手順が異なりますので、各々の手順に従って設定をお願いします。
そして設定の内容を変数に設定します。
ドメインは、ここでは仮にexample.com
にしていますが、お金を出して購入した自分だけのドメインを設定します。くれぐれもこのまま使用しないで下さい。
MY_DOMAIN=example.com
TEST_DOMAIN=test.$MY_DOMAIN
我が家のサーバーの、家庭内LANでのIPアドレスは172.16.1.2
です。
HOST_IPADDR=172.16.1.2
家庭内DNS
最初に、家庭内DNSを設定してしまいます。
cd /etc/unbound/unbound.conf.d/
cat | sudo tee -a machines.list >/dev/null <<___
local-data: "$TEST_DOMAIN. 3600000 IN A $HOST_IPADDR"
___
unbound-checkconf
sudo systemctl restart unbound
Webサーバー
Webサーバーを立てます。基本的な設定ばかりなので、解説無しで一気に行きます。
TEST_PORT=20080
cd
mkdir docker-nginx1
cd docker-nginx1
mkdir -p config/nginx
mkdir -p data/nginx
mkdir html
cat <<___ >docker-compose.yml
version: "3.9"
services:
nginx1:
image: nginx:latest
container_name: nginx1
restart: unless-stopped
ports:
- $TEST_PORT:80
volumes:
- ./config/nginx:/etc/nginx/conf.d
- ./data/nginx:/var/log/nginx
- ./html:/usr/share/nginx/html
___
cat <<___ >config/nginx/test.conf
server {
listen 80;
server_name _;
server_tokens off;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
___
cat <<___ >html/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>nginx test</title>
</head>
<body>
<h1>test</h1>
<p>hello,world</p>
</body>
</html>
___
docker compose up -d
動作確認。
curl http://$HOST_IPADDR:$TEST_PORT/
リバースプロキシ
リバースプロキシですが、概要に書いた通り、certbot
をインストールします。
cd
mkdir docker-proxy
cd docker-proxy
mkdir -p config/nginx
mkdir -p data/nginx
mkdir -p html/ssl-proof
mkdir letsencrypt
cat <<___ >docker-compose.yml
version: "3.9"
services:
proxy:
build: .
container_name: proxy
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- ./config/nginx:/etc/nginx/conf.d
- ./data/nginx:/var/log/nginx
- ./html:/usr/share/nginx/html
- ./letsencrypt:/etc/letsencrypt
logging:
driver: json-file
options:
max-size: 10m
max-file: '3'
___
cat <<___ >Dockerfile
FROM nginx:latest
RUN apt update \
&& apt install -y certbot \
&& apt -y clean
___
cat <<___ >config/nginx/default.conf
server {
listen 80 default_server;
server_name _;
server_tokens off;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
___
cat <<___ >config/nginx/test.conf
server {
listen 80;
server_name $TEST_DOMAIN;
server_tokens off;
location ^~ /.well-known/acme-challenge/ {
root /usr/share/nginx/html/ssl-proof;
}
location / {
proxy_pass http://$HOST_IPADDR:$TEST_PORT;
}
}
___
docker compose up -d
動作確認。
curl http://$TEST_DOMAIN/
certbotで証明書を取得
ここからが本番ですね。まずは証明書取得の手順です。certbot
を実行します。
docker compose exec proxy certbot certonly --register-unsafely-without-email --agree-tos --webroot -w /usr/share/nginx/html/ssl-proof -d $TEST_DOMAIN
証明書等は下記の場所に格納されています。但し権限がroot
で設定されています。
- 公開鍵:
~/docker-proxy/letsencrypt/live/$TEST_DOMAIN/fullchain.pem
- 秘密鍵:
~/docker-proxy/letsencrypt/live/$TEST_DOMAIN/privkey.pem
cd
cd docker-proxy
sudo ls -l letsencrypt/live/$TEST_DOMAIN
こんな感じです。
$ sudo ls -l letsencrypt/live/$TEST_DOMAIN
total 9
-rw-r--r-- 1 root root 692 Nov 28 13:50 README
lrwxrwxrwx 1 root root 42 Nov 28 13:50 cert.pem -> ../../archive/test.example.com/cert1.pem
lrwxrwxrwx 1 root root 43 Nov 28 13:50 chain.pem -> ../../archive/test.example.com/chain1.pem
lrwxrwxrwx 1 root root 47 Nov 28 13:50 fullchain.pem -> ../../archive/test.example.com/fullchain1.pem
lrwxrwxrwx 1 root root 45 Nov 28 13:50 privkey.pem -> ../../archive/test.example.com/privkey1.pem
証明書の中身を確認してみましょう。
sudo openssl x509 -text -in letsencrypt/live/$TEST_DOMAIN/fullchain.pem | grep -E "Issuer:|Subject:"
sudo openssl x509 -dates -noout -in letsencrypt/live/$TEST_DOMAIN/fullchain.pem
リバースプロキシに証明書を設定
転送の部分に証明書を設定します。そして80番ポートは、証明書の保守の為に残しておきます。
cat <<___ >config/nginx/test.conf
server {
listen 80;
server_name $TEST_DOMAIN;
server_tokens off;
location ^~ /.well-known/acme-challenge/ {
root /usr/share/nginx/html/ssl-proof;
}
location / {
return 301 http://$TEST_DOMAIN;
}
}
server {
listen 443 ssl;
server_name $TEST_DOMAIN;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/$TEST_DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$TEST_DOMAIN/privkey.pem;
location / {
proxy_pass http://$HOST_IPADDR:$TEST_PORT;
}
}
___
nginxを再起動します。
docker compose exec proxy nginx -s reload
動作確認。
curl https://$TEST_DOMAIN/
自動更新
証明書更新の設定です。
cd
cd docker-proxy
cat <<___ >certbot-renew.sh
#!/bin/bash
cd
cd docker-proxy
/usr/bin/date -Iseconds
/usr/bin/docker compose exec proxy certbot renew
/usr/bin/docker compose exec proxy nginx -s reload
echo "====="
___
chmod +x certbot-renew.sh
sudo ln -s $PWD/certbot-renew.sh /usr/local/bin/certbot-renew.sh
ログに関しては、logger
コマンドはやめました。標準出力と標準エラー出力をローカルにリダイレクトするだけにしました。それをlogrotate
で回します。
sudo mkdir /var/log/letsencrypt
sudo chown $USER /var/log/letsencrypt
cat <<___ | sudo tee /etc/logrotate.d/certbot-renew >/dev/null
/var/log/letsencrypt/certbot-renew.log {
missingok
rotate 3
compress
monthly
minsize 1M
}
___
Let's Encrypt の証明書は有効期限が90日ですので、月に2回、毎月1日と15日の午前3時に更新チェックを走らせます。残り日数が30日以上かどうかを見て更新すべきかどうか自動的に判断してくれるようなので。
なお、/etc/cron.d
に設定するファイルは、拡張子は無しにしないとダメのようです。
cat <<___ | sudo tee /etc/cron.d/certbot-renew >/dev/null
0 3 1,15 * * $USER /usr/local/bin/certbot-renew.sh >>/var/log/letsencrypt/certbot-renew.log 2>&1
___
後は実際の動作を確認するだけですね。ちょっと息の長い話になります。
cd
cd docker-proxy
sudo ls -l letsencrypt/live/$TEST_DOMAIN/
cat /var/log/letsencrypt/certbot-renew.log
仕舞い
今回も手順のチェックが目的です。動作確認が終わったら、全部綺麗に消しましょう。
sudo rm /etc/cron.d/certbot-renew
sudo rm /etc/logrotate.d/certbot-renew
sudo rm /usr/local/bin/certbot-renew.sh
sudo rm -r /var/log/letsencrypt
cd
cd docker-proxy
docker compose down --rmi all --volumes
cd
cd docker-nginx1
docker compose down --rmi all --volumes
cd
sudo rm -r docker-proxy docker-nginx1
sudo sed -i -e"/$TEST_DOMAIN./d" /etc/unbound/unbound.conf.d/machines.list
unbound-checkconf
sudo systemctl restart unbound
今回は Let's Encrypt で証明書を取得し、証明書を維持する、それをリバースプロキシの環境下で設定する手順を確認できました。