Let's Encrypt で Nginx にSSLを設定する


Let's Encrypt で取得したサーバ証明書を Nginxに設定するための手順。

確認した環境は次の通り。

- OS: CentOS 7.2
- Nginx 1.11

## Let's Encrypt 導入の事前準備
証明書を取得したいドメインでアクセスできるサーバを立てておく。
アプリケーションやHTTPサーバのインストールは必ずしも必要ないが、次の2点が必要。

- 取得したいドメインがDNSで名前解決できる
- 80番ポートでアクセスできる

以下の操作はそのサーバ上で行う。

## certbot のインストール
まず、Let's Encrypt のクライアント`certbot`をインストール。
インストールと言っても、git で cloneするだけ。
以下の例では、/usr/local にインストールしているが、場所はどこでもよい。

```console
$ cd /usr/local
$ git clone https://github.com/certbot/certbot
$ cd certbot
$ ./certbot-auto --help
Usage: certbot-auto [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
to both this script and certbot will be downloaded and installed. After
ensuring you have the latest versions installed, certbot will be invoked with
all arguments you have provided.

Help for certbot itself cannot be provided until it is installed.

  --debug                                   attempt experimental installation
  -h, --help                                print this help
  -n, --non-interactive, --noninteractive   run without asking for user input
  --no-self-upgrade                         do not download updates
  --os-packages-only                        install OS dependencies and exit
  -v, --verbose                             provide more output
```

上の例の様に `-h` オプションでヘルプが表示されればひとまずOK。

## 証明書の取得

以下に次の環境の証明書の取得の手順を示す。HTTPサーバも用意する必要がないスタンドアローンモードを利用する例である。

- ドメイン：www.hogehoge.com

```console
$ ./certbot-auto certonly --standalone -t
```

初回実行時には不足パッケージのインストールを促される。
yesと答えてインストールする。

不足パッケージのインストールが終わると、次の様にメールアドレスを聞いてくるので入力する。

```bash
Creating virtual environment...
Installing Python packages...
Installation succeeded.
Enter email address (used for urgent notices and lost key recovery) (Enter 'c' to cancel): xxxx@hogehoge.com
```

メールアドレスの次は、ライセンスに Agreeするかを聞いてくる。

```bash
-------------------------------------------------------------------------------
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf. You must agree
in order to register with the ACME server at
https://acme-v01.api.letsencrypt.org/directory
-------------------------------------------------------------------------------
(A)gree/(C)ancel: A
```

上の様に`A`を入力してAgreeすると続いて次の様にドメインを訪ねてくる。
取得したいドメインを入力する。そしてこのドメイン名で操作しているサーバが名前解決できる必要がある。

```bash
Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c' to cancel):www.hogehoge.com
```

この例では スタンドアローンモードを指定しているので、`certbot-auto` は自前でHTTPサーバを起動し、認証用のファイルを取りにこさせる。この時点でTCP/80をバインドするプロセスがあればエラーで止まる。
ファイルを取らせることができれば認証成功。次の様に「Congratulations!」とメッセージが表示される。

```
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/www.hogehoge.com/fullchain.pem. Your cert
   will expire on 2016-11-11. To obtain a new or tweaked version of
   this certificate in the future, simply run certbot-auto again. To
   non-interactively renew *all* of your certificates, run
   "certbot-auto renew"
 - If you lose your account credentials, you can recover through
   e-mails sent to xxxx@hogehoge.com.
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - 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
```

## インタラクティブなやり取りはしたくない場合
上記のようにプロンプトに入力を求められることなく、証明書を発行したい時には次のオプションを付けて実行する。Ansible等で設定したい時には入力を求められた時点で止まってしまうので、`--agree-tos`をつけると良い。利用規約にAgreeして処理が進む。 

```bash
./certbot-auto certonly --standalone -d www.hogehoge.com -m admin@www.hogehoge.com --agree-tos -n
```

### 取得した証明書一式
cert-botにより取得した証明書一式は `/etc/letsencrypt/` 以下に次のようなディレクトリ構成で保存されている。
webサーバの設定で指定するのは`/etc/letsencrypt/live/`以下にあるシンボリックリンク。

```bash
/etc/letsencrypt/
├── accounts/ # アカウント情報
├── archive/  # 取得した証明書の実体がドメインごとのサブディレクトリに保存されている
│   └── www.hogehoge.com/
├── csr
├── keys
├── live/     # 最新の証明書へのシンボリックリンクが作られる。
│   └── www.hogehoge.com
│       ├── cert.pem -> ../../archive/www.hogehoge.com/cert2.pem
│       ├── chain.pem -> ../../archive/www.hogehoge.com/chain2.pem
│       ├── fullchain.pem -> ../../archive/www.hogehoge.com/fullchain2.pem
│       └── privkey.pem -> ../../archive/www.hogehoge.com/privkey2.pem
└── renewal/  # 証明書更新のための設定が保存されている。
    └── www.hogehoge.com.conf
```

## Nginxの設定
取得した証明書をNginxに設定してSSLを有効にする。
次の例は、最低限、追加する必要がある設定。

```Nginx
server {
  ...
  listen 443 ssl;
  ssl_certificate     /etc/letsencrypt/live/www.hogehoge.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.hogehoge.com/privkey.pem;
  ...
}
```

`ssl_certificate`にはサーバ証明書と中間証明書が結合された`fullchain.pem`のパスを設定する。
`ssl_certificate_key`には証明書と対の秘密鍵である`privkey.pem`のパスを指定する。
証明書を指定する場合、実体の`/etc/letsencrypt/archive/`以下のファイルは後述の証明書の更新をするとファイル名が変わってしまうので、`/etc/letsencrypt/live/`以下のシンボリックリンクを指定する。

設定したら Nginxを restartして反映する。
上記の設定は最低限のもの。[SSL Server Test \(Powered by Qualys SSL Labs\)](https://www.ssllabs.com/ssltest/index.html)のスコアでAとかA+を取りたいなら、次のサイト(検証付きで設定を解説してくれている)を参考に設定を追加すれば良い。

- [Webサーバー nginx における SSL証明書設定の安全性向上 ～SSL Server Test で A\+ 判定を目指して～SaintSouth\.NET \| SaintSouth\.NET](https://www.saintsouth.net/blog/safety-of-ssl-certificate-setting-improvements-in-web-server-nginx-to-get-rankaplus-from-ssl-server-test/)

とりあえず、ここまでで、Let's EncryptによるSSL化は終了。

## 証明書の更新
Let's Encryptで取得できる証明書は有効期限が3ヶ月と短い。なので、定期的に更新する必要がある。
そのために、`certbot-auto`には`renew`サブコマンドがある。

実行してみると次のような結果となる。

```console
$ ./certbot-auto renew

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/www.hogehoge.com.conf
-------------------------------------------------------------------------------

The following certs are not due for renewal yet:
  /etc/letsencrypt/live/www.hogehoge.com/fullchain.pem (skipped)
No renewals were attempted.
```

上記のようにまだ、更新するほど古くないのでスキップしたよって表示されればOK。

`renew`の結果にもあるように `/etc/letsencrypt/renewal/www.hogehoge.com.conf`に記録されている設定にしたがって、証明書を更新しようとする。このエントリの例では`--standalone`を指定したので、`renew`でも`--standalone`で動作する。が、実運用するNginxがある場合、ちょっと困ったことになる。`certbot-auto`が80番ポート利用するために、Nginxが80番ポートをバインドしている場合に止めなければならなくなる。

それを避けるには`--webroot`を利用する。このモードはドキュメントルート以下に`/.well-known/acme-challenge/`というディレクトリを作ってその中に認証用のファイルを書き出し、letsencryptのサイトから取らせることにより認証しようとする。サービスで使っているNginxを利用するため止める必要がなくなる。

一旦、`--standalone`で実行して作成された設定ファイル`/etc/letsencrypt/renewal/www.hogehoge.com.conf`を`--webroot`のものに書き換えるには次の様に`--webroot`を指定し、`force-renewal`をつけて実行すると設定ファイルが上書きされる。

```bash
./certbot-auto certonly --webroot -w /var/www/hogehoge -d www.hogehoge.com --agree-tos --force-renewal -n
```

`--webroot`を利用する場合には、認証ファイルを取れる状態にNginxを設定しておく必要がある。
`renew`の結果に`All renewal attempts failed.`と表示されて失敗するときには、認証用ファイルが取れる状態かどうかを確認すると良い。
うまくいかない時には次のようなlocationを追加してみる。

```Nginx
server {
  ...
  location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    root         {{doc_root}};
  }
  ...
}
```

昨今の常時HTTPS化の流れで、HTTPでアクセスされたら HTTPSにリダイレクトするなどHTTPS通信を強制する場合も多くなっていると思う。
ただ、Let's Encryptの証明書の認証ファイルのチェックは常に **HTTP** でアクセスしてくる。したがって`--webroot`で更新しようとするなら、次のように `/.well-known/acme-challenge/`から始まるパスへのリクエストはHTTPで受けつつ、その他はすべてHTTPSにリダイレクトするようにすれば良い。

```Nginx
server {
    listen 80 default_server;

    location ^~ /.well-known/acme-challenge/ {
      default_type "text/plain";
      root         {{doc_root}};
    }

    location / {
      # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
      return 301 https://$host$request_uri;
    }
}
```

### 更新の自動化

`./certbot-auto renew`で失敗しないなら次のようなcronを設定して更新を自動化する。

```crontab
0 4 1 * * /usr/local/certbot/certbot-auto renew && /bin/systemctl reload nginx
```
これは毎月1日の4:00に更新する例。
証明書の更新後、Nginxをリロードして反映することを忘れずに。

## 最後に
運用時には `--webroot`で renewal できるようにしたほうが良いと思う。でも、最初の証明書取得は`--standalone`のほうがやりやすいのではないかと思う。
`--webroot`で証明書を取得するには、Nginxを起動する必要があるが、`ssl_certificate`や`ssl_certificate_key`で指定したファイルが存在しないとエラーで起動しない。でもまだ証明書はない。このジレンマ。
だったら、最初は`--standalone`で取得して、SSL設定した後でNginxを起動したほうが楽じゃないかなと思う。で、renewalは`--webroot`で行う。

## 参考:renewal用の設定ファイルの内容

### standalone用
```ini:www.hogehoge.com.conf
# renew_before_expiry = 30 days
version = 0.8.1
cert = /etc/letsencrypt/live/www.hogehoge.com/cert.pem
privkey = /etc/letsencrypt/live/www.hogehoge.com/privkey.pem
chain = /etc/letsencrypt/live/www.hogehoge.com/chain.pem
fullchain = /etc/letsencrypt/live/www.hogehoge.com/fullchain.pem

# Options used in the renewal process
[renewalparams]
authenticator = standalone
installer = None
account = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
webroot_path = /var/www/hogehoge,
[[webroot_map]]
```

### webroot 用
```ini:www.hogehoge.com.conf
# renew_before_expiry = 30 days
version = 0.8.1
cert = /etc/letsencrypt/live/www.hogehoge.com/cert.pem
privkey = /etc/letsencrypt/live/www.hogehoge.com/privkey.pem
chain = /etc/letsencrypt/live/www.hogehoge.com/chain.pem
fullchain = /etc/letsencrypt/live/www.hogehoge.com/fullchain.pem

# Options used in the renewal process
[renewalparams]
authenticator = webroot
installer = None
account = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
webroot_path = /var/www/hogehoge,
[[webroot_map]]
www.hogehoge.com = /var/www/hogehoge
```

## サブコマンド・オプションの説明
このエントリで使った `certbot-auto` のサブコマンドとオプションをざっくり説明する。
詳しくは[コマンド解説 \- Let's Encrypt 総合ポータル](https://letsencrypt.jp/command/#renew)を見ると良い。

- `certonly`：証明書の取得だけを行うコマンド。
	- 他にインストールまでしてくれる`run`コマンドなどがある。
- `--webroot`：webサーバのドキュメントルートに書き出される認証用ファイルで認証を行う。
	- 他に`--standalone`があるが、これは80番ポートをバインドしている他のプロセス(webサーバなど)を止める必要があるのでwebサーバのために証明書を取得するときには`--webroot`を使うと良い。
- `-w`：ドキュメントルートを指定する。`renew`の時、ドキュメントルート以下の `/.well-known/acme-challenge/`ディレクトリに認証用
- `-d`：取得する証明書のドメインを指定する。このドメインでアクセスできるwebサーバが必要なので先に準備しておく。
- `-t`：[TUI](https://ja.wikipedia.org/wiki/%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%A6%E3%83%BC%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9)を使わずにテキストベースで設定することを指定する。
	- これを指定しない場合、TUIが起動する。(`certbot-auto`はTUIがデフォルト)
- `--agree-tos`：ACME 利用規約に同意する。インタラクティブに尋ねられないのでスクリプト等を使って設定する場合には便利。
- `-n`： ユーザの入力を求められないノンインタラクティブモードで実行される。`-d`、`-w`、`-m`等必須なパラメータを付ける必要がある。足りない場合にはつけるように促すエラーメッセージが表示される。
- `--force-renewal`: 証明書の更新を強制する。これをつけなければ有効期限が十分ある場合、更新されない。

## 参考
- [Let's Encrypt の使い方 \- Let's Encrypt 総合ポータル](https://letsencrypt.jp/usage/)
- [光の速さのWEBサーバー\(nginx\)をlet's encryptでSSL化及びHTTP/2化。ついでにセキュリティ評価をA＋にする。 \- Qiita](http://qiita.com/sak_2/items/ff835b669c0a7e110b09)