nginxリバースプロキシ環境でLet's EncryptによるSSL/TLS化

nginxを使用したリバースプロキシ環境でのLet's Encryptによる証明書発行とSSL/TLS化の設定、および証明書の更新手順を書きます。nginxはCentOS7のネイティブで動作しており、バックエンドにWEBサーバがあり、SSL/TLSはリバースプロキシnginxが担当するとします。バックエンドのサーバはSSL/TLSの処理をせずに済むのでパフォーマンスを保ったまま運用できます。

[Let’s Encrypt](https://letsencrypt.org/ja/)
[Let's Encrypt 総合ポータル](https://free-ssl.jp/)

Let's Encryptはフリーで証明書を発行してくれる認証局です。証明書有効期限は90日と短いですが、コマンドで自動更新が可能です。

リバースプロキシを使用しない場合の証明書の発行手順を確認
---
まず初めにLet's Encryptから証明書発行してもらうまでの手順をわかっておきます。certbotクライアントを使用します。

```text:certbotのインストール
# yum install epel-release -y
# yum install certbot - y
```

certbotがインストールできたら、証明書を取得したいドメイン名と、対応したDocumentRootを引数にしてコマンドを実行します。今回証明書を取得したいドメイン名は`example.jp`であるとします。


```text:証明書発行コマンド
# certbot certonly --webroot -w /var/www/html -d example.jp
```

####オプションについて
`--webroot`オプションを指定すると、既存のWEBサーバのDocumentRoot配下に`/.well-known/acme-challenge/`というディレクトリが作成され、そのディレクトリ配下のファイルをLet's Encryptが参照できる事で検証成功・証明書が発行されます。
[チャレンジのタイプ - Let's Encrypt](https://letsencrypt.org/ja/docs/challenge-types/)

上記の例では`/var/www/html`を`example.jp`として公開しており、Let's Encryptが80ポートで(`http://example.jp/.well-known/acme-challenge/`配下に)アクセス確認ができると証明書が発行されます。※よって取得したいドメイン名がDNSで名前解決できる必要があります。


`--webroot`ではなく`--standalone`オプションを指定するとcertbotがWEBサーバとなってくれる為、別途WEBサーバ不要で証明書発行が可能です。WEBサーバ稼働前など初回の証明書発行であればこちらの方が簡単ですが、既に何らかのWEBサーバが稼働していて80ポートが使用されている場合は使えません。環境にあった手段を用いると良いと思います。

`-d`オプションは証明書を取得したいFQDNを指定します。複数指定することで、複数のサブドメインに対応した証明書を発行してもらえます。

サブドメインに`www.example.jp`を使いたい場合は以下のコマンドになります。

```text:複数のサブドメインに対応した証明書発行コマンド
# certbot certonly --webroot -w /var/www/html -d example.jp -d www.example.jp
```

※証明書は指定したFQDNに対してのみ発行されるので、例えばサブドメイン`www.example.jp`を持ったサーバに対して`example.jp`のみ対象とした証明書をインストールした場合、`https://www.example.jp/`に接続しようとしても「この接続は安全ではありません」となります。

リバースプロキシサーバで証明書取得
---
nginxでのリバースプロキシに導入する場合の手順を記載します。証明書をインストールしたいサーバは(バックエンドのWEBサーバではなく)リバースプロキシサーバ側なので、上記のDocumentRootは存在しません。なのでnginx側に、プロキシ動作しないwebrootを作成します。

初めにWebRoot用のパスを作成します。

```
# cd /usr/share/nginx/html
# mkdir ssl-proof
# chmod 755 ssl-proof
```

作成したらnginxのlocationディレクティブに`/.well-known`のrootパスとして設定します。

```text:/etc/nginx/conf.d/server.conf
upstream backend {
    server xxx.xxx.xxx.xxx:80;
}

server {
    listen         80;
    server_name    .example.jp;

    location ^~ /.well-known {
        root /usr/share/nginx/html/ssl-proof;
    }

    location / {
        proxy_pass http://backend/;
    }
}
```

**nginxのlocationディレクティブは複数定義した場合、指定したプレフィックスによって優先順位が異なります。記述順が最優先ではない為、注意が必要です。**

[nginx locationの評価順序](https://qiita.com/aosho235/items/1acf65246d2a36661021)
[prefixの優先順位](http://www2.matsue-ct.ac.jp/home/kanayama/text/nginx/node34.html)

`/.well-known`へのアクセスは、マッチしたら評価を終えたいので、`^~`(前方一致、以降の正規表現を評価しない)を指定します。

URLに`http://example.jp/.well-known`を含むアクセスは`/usr/share/nginx/html/ssl-proof`をルートパスとして振舞いますが、それ以外へのアクセスはバックエンドWEBサーバに送ります。

上記の設定に基づいたcertbotのコマンドは以下になります。

```text:証明書発行コマンド
# certbot certonly --webroot -w /usr/share/nginx/html/ssl-proof -d example.jp
```



nginxへの証明書の設定
---
Let's Encryptの検証が成功すると、以下のディレクトリに証明書関係のファイルが作成されます。

```
/etc/letsencrypt/
└ archive/
  └ (ドメイン名)/　# 証明書ファイル本体
    └ (ファイル名末尾の数字は更新回数)
└ live/
  └ (ドメイン名)/　# (最新の)証明書ファイルに対するシンボリックリンク
    └ cert.pem(サーバ証明書)
    └ chain.pem(中間証明書)
    └ fullchain.pem(サーバ証明書と中間証明書を連結したファイル)
    └ privkey.pem(秘密鍵)
└ renewal/
  └ (ドメイン名).conf  # 証明書の更新用の設定ファイル
```

nginxの設定で指定する証明書ファイルのパスは、シンボリックリンクの方です。fullchain.pemの中身はcert.pemとchain.pemを繋げたものです。nginxでは連結ファイルを指定できます。秘密鍵を一緒に指定します。

```text:/etc/nginx/conf.d/server.conf
upstream backend {
    server xxx.xxx.xxx.xxx:80;
}

server {
    listen         443 ssl;
    server_name    example.jp;

    ssl_certificate     /etc/letsencrypt/live/example.jp/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.jp/privkey.pem;

    location / {
        proxy_pass http://backend/;
    }
}
```

初回の証明書発行までの手順は上記の通りです。が、冒頭に述べたようにLet's Encryptの証明書有効期限は90日となっており、その都度、証明書の再発行／更新が必要になります。更新のコマンドは以下の通りです。

```
# certbot renew
# systemctl reload nginx
```

更新が成功すると、`/etc/letsencrypt/archive/(ドメイン名)/`配下に証明書と秘密鍵に更新回数が振られたファイルが作成され、`/etc/letsencrypt/live/(ドメイン名)/`配下のシンボリックリンクが更新されます。

証明書を更新した後は、nginxをリロードする必要があります。cronなどで自動実行する場合は忘れないように。

更新の設定は`/etc/letsencrypt/renewal/(ドメイン名).conf`に基づきます。WebRootのパス設定もあるので、もし変更がある場合は直接編集してください。

尚、証明書を更新しなければならない場面では、既に80ポート接続を443ポート(https)にリダイレクトさせている状態になっている事が殆どと思います。Let's Encryptはポート80に接続に来ますので、ポート80の`/.well-known`のlocation設定とrootパスは残しておいてください。

```text:最終的な/etc/nginx/conf.d/server.conf
upstream backend {
    server xxx.xxx.xxx.xxx:80;
}

server {
    listen         80;
    server_name    example.jp;

    location ^~ /.well-known {
        root /usr/share/nginx/html/ssl-proof;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen         443 ssl;
    server_name    example.jp;

    ssl_certificate     /etc/letsencrypt/live/example.jp/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.jp/privkey.pem;

    location / {
        proxy_pass http://backend/;
    }
}
```

※2020.07.13 httpsのリダイレクト部分の正規表現指定が漏れており、.well-knownにパスが通ってもhttpsにリダイレクトされてしまう設定を記載していましたので修正しました。

上記`server.conf`は最低限の設定のみ記載していますので、sslやproxyの詳細は環境に応じて設定してください。後日、別記事で自分用の備忘録を作る予定。

別記事：[nginxでのSSL/TLS設定の備忘録](https://qiita.com/__juiblex__/items/67171d9e83fcd38bf3cb)

[Module ngx_http_ssl_module](http://nginx.org/en/docs/http/ngx_http_ssl_module.html)
[Module ngx_http_proxy_module](http://nginx.org/en/docs/http/ngx_http_proxy_module.html)