1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

私的サーバー構築日誌:nginx on docker のリバースプロキシ環境で Let's Encrypt

Last updated at Posted at 2023-12-12

能書き

私的サーバー構築日誌:仕切り直しからの自宅サーバー構築の続きです。

表題通りです。前回は nginx on docker でリバースプロキシを立ててみましたが、この環境下で Let's Encrypt の証明書を取得し、維持する手順を模索します。

調べてみましたが、似たような内容の記事は案外少なかったですね。特に維持の手順。私も手順を考えてみましたが、どうもDockerとは馴染みにくい方法しか思い付きませんでした。そして試行錯誤の挙句、ホストマシンのcronとの組合せになりました。

目標

  • Dockerコンテナで立てたnginxリバースプロキシを前提とする
  • リバースプロキシに証明書を持たせて、上流(upstream)にはHTTPで接続。
  • 証明書を Let's Encrypt で取得(certbotを利用)
  • 証明書を定期的に自動更新する。

概要

証明書の取得と維持の為に、リバースプロキシのDockerコンテナにcertbotをインストールしてしまいます。後は標準的な手順通り。

本当は、褒められた方法ではない事を承知で、Dockerコンテナにcronを入れたかったのですが。実際にやってみると、リバースプロキシが動かなくなります。困ったものです。

そういう訳で、コマンドの定期起動は、ホストマシンのcron設定で実現する事にします。

こういう事をすると後で忘れるので避けたかったんですけどね。リバースプロキシだけでLXDコンテナにするのも大袈裟に過ぎる気がしますし。残念です。

参考文献

Dockerコンテナでcronを使う方法は、今回はやめましたが。参考文献だけ一応挙げておきます。

実験環境を構築

前回と同じ実験環境を構築します。リバースプロキシにおいて若干の手順変更がある為、改めて全手順を掲載します。

インターネット設定

外のインターネットから、我が家のサーバーにアクセスできるように設定します。まずは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 で証明書を取得し、証明書を維持する、それをリバースプロキシの環境下で設定する手順を確認できました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?