Certbot で新ドメインに Let's Encrypt SSL を適用

サーバー上で使っている複数のドメインの内、一部に対して Let's Encrypt が適用されていなかったので、追加適用しました。

Ubuntu 20.04 で Web サーバーとして Nginx を使っています。

すでに Certbot はインストール済みです。

自動更新タイマーの確認

「sudo systemctl status certbot.timer」を実行して、Certbot の自動更新のタイマーが動いているか確認します。

$ sudo systemctl status certbot.timer
[sudo] password for ユーザー名: 
● certbot.timer - Run certbot twice daily
     Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; vendor preset: enabled)
     Active: active (waiting) since Wed 2022-05-18 22:15:43 JST; 5 months 2 days ago
    Trigger: Fri 2022-10-21 07:14:04 JST; 9h left
   Triggers: ● certbot.service

「Active: active (waiting) since Wed 2022-05-18 22:15:43 JST; 5 months 2 days ago」となっている通り、タイマー自体は動いています。

新ドメインへの Let's Encrypt 適用

「$ sudo certbot」を実行します。

$ sudo certbot
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx

Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: www.graffuhs.com
2: example.com
3: sub1.example.com
4: sub2.example.com
5: sub3.example.com
6: www.example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 

どのドメインに対して HTTPS を有効にするか聞かれます。

今回は「sub3.example.com」に適用したいので「5」と入力しました。

Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 5
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for sub3.example.com
Waiting for verification...
Cleaning up challenges
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/example.com

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):

Web サーバーの設定に対して HTTP → HTTPS へのリダイレクトを行うか聞かれますが、実は設定ファイル上ではすでにリダイレクト設定はしておいたので、今回は「1」(リダイレクトなし)とします。

Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 1
Future versions of Certbot will automatically configure the webserver so that all requests redirect to secure HTTPS access. You can control this behavior and disable this warning with the --redirect and --no-redirect flags.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://sub3.example.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=sub3.example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

特にエラーもなく、これで完了です。ブラウザで確認してみても鍵マークがついていますね。

ちなみに

もう特にする事はないですが、上記メッセージの最後の行にサイトをテストできる URL が貼ってあったので飛んでみました。

こんな感じです。

それから、証明書と chain(?)、そして鍵ファイルの場所が書いてありました。直接アクセスはできませんでしたが、システム上こんな感じで管理されている様です。

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/sub3.example.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/sub3.example.com/privkey.pem
   Your cert will expire on 2023-01-18. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot again
   with the "certonly" option. 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

【Django】Nginx + Gunicorn でサブドメインを設定する方法

「example.com」というドメイン名ですでに運用しているところに追加で「sub.example.com」も使いたいという想定で書きます。

また、Django の処理には Gunicorn を使っています。

  1. ネームサーバーでサブドメインの設定をする
  2. サブドメイン用のディレクトリを作る
  3. 仮想環境と Django プロジェクトを作成する
  4. Gunicorn をインストールする
  5. UNIX ドメインソケットを設定する
  6. Nginx の設定ファイルにサブドメインの処理を追記する
  7. とりあえず図にしてみた

1. ネームサーバーでサブドメインの設定をする

ネームサーバーでサブドメインを登録します。

フィールド
エントリ名sub
種別別名 (CNAME)
@
DNSチェックする
TTLの設定なし

さくらインターネットを使っている場合は会員メニュー > ドメイン > ゾーン表示の画面で登録できます。

2. サブドメイン用のディレクトリを作る

任意の場所にサイトをホストするためのディレクトリを作成します。

例えば「example.com」というディレクトリの配下にさらにサブドメイン用に「sub」ディレクトリ、その配下に「html」と作る場合は下記の様な形。

$ cd var/www/example.com
$ mkdir sub
$ cd sub
$ mkdir html

ディレクトリの管理者を変更します。

$ sudo chown -R $USER:$USER /var/www/example.com/sub/html/

注意

一度上記の管理者の変更を飛ばした際、後で Django の「python manage.py startapp アプリ名」を実行した時に「ImportError: Couldn't import Django. Are you sure it's installed and available on your PYTHONPATH environment variable? Did you forget to activate a virtual environment?」というエラーが出ました。

「pip install django」を実行した際に django モジュールが仮想環境内に上手くインストールされなかったことが原因です。

3. 仮想環境と Django プロジェクトを作成する

Python の仮想環境を作成、起動します。

$ cd var/www/example.com/sub/html/
$ python3.9 -m venv dj_proj_venv
$ cd dj_proj_venv
$ source bin/activate

Django を pip install し、プロジェクトとアプリケーションを作成します。

注)ローカルPCで開発したものを Git で本番環境にクローンする様な場合はこのステップは不要です。

(dj_proj_venv) $ pip install django
(dj_proj_venv) $ django-admin startproject dj_proj
(dj_proj_venv) $ cd dj_proj
(dj_proj_venv) $ python manage.py startapp dj_app

settings.py の INSTALLED_APPS にアプリケーションを追記します。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'dj_app.apps.DjAppConfig', # 追記分
]

settings.py の ALLOWED_HOSTS にサブドメインを含むドメインを記述します。

ALLOWED_HOSTS = ['sub.example.com']

4. Gunicorn をインストールする

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

$ pip install gunicorn

5. UNIX ドメインソケットを設定する

そして Nginx と Gunicorn の接続に Unix ソケットを使える様、Linux サーバーの Systemd という機能で設定します。

.socket ファイルの作成

サブドメイン用に「example_sub.socket」というファイルを作成します。

# example_sub.socket
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/example_sub.sock
[Install]
WantedBy=sockets.target
項目メモ
Descriptionログ出力の際などに使われる
ListenStreamリクエストを待ち受けるポートの指定。
Nginx 設定ファイルの pass_proxy で指定するものと同じ。
WantedBysockets.target

.service ファイルの作成

サブドメイン用に「example_sub.service」というファイルを作成します。

上記の「example_sub.socket」へ送られてきたリクエストの引き渡し先を定義します。

# example_sub.service
[Unit]
Description=gunicorn daemon
Requires=example_sub.socket
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=/var/www/example.com/sub/html/dj_proj_venv/dj_proj
ExecStart=/var/www/example.com/sub/html/dj_proj_venv/bin/gunicorn --workers 3 --bind unix:/run/sample_django.sock dj_proj.wsgi:application
[Install]
WantedBy=multi-user.target
項目メモ
Descriptionログ出力の際などに使われる
Requires対応する .socket ファイル
Afterユニットが開始する順序 (ユニットの起動する順番) を設定。
After で指定したユニットがアクティブになると、このユニットを開始する。
User
Group
WorkingDirectorymanage.py があるディレクトリのフルパス?
ExecStartsystemctl start した時に実行するコマンド。
「$ gunicorn [OPTIONS] [WSGI_APP]」のフォーマットで記述。
上記ファイルでは venv 内の gunicorn のフルパスを指定し、オプションとして --workers と --bind を指定した上で Django の wsgi を実行している。
--workers:worker プロセスの数
--bind:bind 対象のサーバーソケット(.sock)。gunicorn のデフォルトは 127.0.0.1:8000
WantedBy大抵 multi-user.target で大丈夫

socket の待ち受けを開始します。

$ systemctl start example_sub.socket
$ systemctl start example_sub.service

6. Nginx の設定ファイルにサブドメインの処理を追記する

「sub.example.com」と「example.com」の処理が記述されています。

それぞれの proxy_pass の記述を見るとわかりますが、「sub.example.com」へのリクエストの場合は example_sub.sock のソケット、「example.com」へのリクエストの場合は example.sock のソケットへ処理が回されます。

server {
        listen 80;
        listen [::]:80;

        server_name sub.example.com;

        root /var/www/example.com/sub/html;
        index index.html;

        location / {
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarder-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_pass http://unix:/run/example_sub.sock;
        }

        location /static {
                alias /var/www/example.com/sub/html/static;
        }

}

server {
        listen 80;
        listen [::]:80;

        server_name example.com;

        root /var/www/example.com/html;
        index index.html;

        location / {
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarder-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_pass http://unix:/run/example.sock;
        }

        location /static {
                alias /var/www/example.com/html/static;
        }

}
ファイル内の概念解説
listenリクエストを受け入れる IP アドレスやポートの指定。
UNIX ドメインソケットのパスでも可。
server_nameリクエストを受け入れるサーバー名(ドメイン名)の指定。
複数のサーバー名をスペース区切りで設定可。
proxy_set_headerプロキシサーバーへ送るリクエストヘッダーの各フィールドの追加や書き換え。
$proxy_add_x_forwarded_forクライアントからのリクエストヘッダーの X-Forwarded-For フィールドに $remote_addr が追加されたもの。
クライアントからのリクエストヘッダーに X-Forwarded-For が無かった場合は $remote_addr と同じ。
$schemeリクエストスキーム。http か https。
proxy_passプロキシサーバーの指定。Gunicorn につなげるための UNIX ソケットを指定。
参照 Nginx Documentation

7. とりあえず図にしてみた

自分の理解のためにいろんなケースを図にしてみました。

1 つのアプリを Gunicorn のデフォルトポートで実行する場合

まずは一番シンプルな形。サブドメインとかなく、一つのドメインで一つのアプリを公開する形。

Gunicorn のデフォルトの bind 先は 127.0.0.1:8000 だそうなので、そこに対して Nginx から proxy_pass した場合です。

Django + Nginx + Gunicorn での設定

1 つのアプリを UNIX ドメインソケットで実行する場合

UNIX ドメインソケットの方が処理が速いということで(?)大半の記事で UNIX ドメインソケットを使用しているっぽいです。なので自分もこの方法を取っています。

この場合、当記事にも書いてある様に Systemd の .socket ファイルと .service ファイルを作成する必要があります。たぶん。実は必須ではないのかも。わかりません。

Django + Nginx + Gunicorn での設定

2 つのアプリを別々の UNIX ドメインソケットで実行する場合

複数のアプリをサブドメインで分けるという事でこの記事で紹介した方法がこちらです。

別々のソケットを用意し、そこに各サブドメインから proxy_pass で繋いであげれば OK です。

Django + Nginx + Gunicorn での設定

2 つのアプリを別々のポートで実行する場合

ただ、上記が出来るならそもそも UNIX ドメインソケットを使わなくても localhost(127.0.0.1)のポートを分ければ良いんじゃないかと思いました。自分では実際に試してませんが多分いける気がします。

Django + Nginx + Gunicorn での設定