Nginx+Let's Encrypt(Certbot) on Docker

Alpine版NginxコンテナのSSL対応

Let’s EncryptによるSSL認証用ファイルをCertbot(Automatic Certificate Management Environment : ACME Client)により獲得し、NginxによるウェブサーバをSSL対応させます。

NginxのDockerイメージ作成時にDockerfileによりCertbotのインストールを指定します。

Alpine版Certbot(Nginx)

https://pkgs.alpinelinux.org/package/edge/community/x86/certbot-nginx

RUN apk add --no-cache bash nano awstats apache2-utils certbot-nginx

または、Nginxコンテナ稼働後、インストールして下さい。

# apk add certbot-nginx

Nginx設定ファイルの異なるserver_nameを持つserverセクション毎に、SSL認証ファイルを獲得するため、Nginxコンテナ内で以下コマンドを実行します。

# certbot --nginx -d www.testsite1.com -d www.testsite2.com

各サーバセクションにSSLアクセス(443)時に必要な認証ファイルディレクトリなどが追加されます。

server {
    listen 80;
    server_name www.testsite1.com;

    location / {
        proxy_pass http://your_server_ip:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/www.testsite1.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.testsite1.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
    listen 80;
    server_name www.testsite2.com;

    location / {
        proxy_pass http://your_server_ip:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/www.testsite2.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.testsite2.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

Nginx設定ファイルをリロードして動作を確認します。

# nginx -s reload

<参考サイト>

certbot renewコマンドによる証明書更新

https://certbot.eff.org/docs/using.html#renewing-certificates

crontabによる更新

https://certbot.eff.org/docs/using.html#automated-renewals

注) 以下コマンドで更新の際は nginx で有効になっているドメインを再確認すること。無効なドメインが残っていると更新プロセスに影響があります。無効なドメインはdeleteコマンドで削除します。

# certbot certificates

nginxコンテナにインストールしたcertbotで更新する場合は、ホストマシンからcrontabで実行スケジュールを指定します。

毎週月曜日1:00,1:05に実行

$ sudo crontab -e

#certbot in nginx docker
0 1 * * 1 docker exec nginx bash -c "certbot renew >> /var/log/letsencrypt/renew.log"
5 1 * * 1 docker exec nginx bash -c "nginx -s reload"

または、

$ sudo crontab -e

#certbot in nginx docker: for the certification renewal, execute "certbot renew" or "certbot --nginx -d www.example1.com -d www.example2.com"
@monthly docker exec nginx bash -c "certbot --nginx -d www.example1.com -d www.example2.com"

更新頻度の目安

以下のFAQによると証明書は90日で期限切れとなるため60日毎の更新を推奨しています。

What is the lifetime for Let’s Encrypt certificates? For how long are they valid?

証明書の削除 “certbot delete”

# certbot delete
Saving debug log to /var/log/letsencrypt/letsencrypt.log

Which certificate(s) would you like to delete?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: www.test001.com
2: www.test002.com
3: www.test001.org
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): c

または、

# certbot revoke --cert-name www.test001.com
# certbot --help
manage certificates:
    certificates    Display information about certificates you have from Certbot
    revoke          Revoke a certificate (supply --cert-path or --cert-name)
    delete          Delete a certificate

Certbot-Dockerhub

https://hub.docker.com/r/certbot/certbot/

Dockerコンテナによる運用
https://certbot.eff.org/docs/install.html#running-with-docker

スタンドアローンモード(オプション)
ウェブサーバ以外の用途で認証を取得する場合のオプションです。
認証取得には、ポート:80をオープンにする必要があるため、ウェブサーバ等は停止してから実行すること。

https://certbot.eff.org/docs/using.html#standalone

certonly:単に指定ドメインの認証を取得したいだけの場合

$ sudo docker run -it --rm --name certbot \
            -v "$PWD/letsencrypt:/etc/letsencrypt" \
            -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
            -p 80:80 \
            certbot/certbot certonly --standalone -d stun.example.com

コンテナ内の /etc/letsencrypt/live ディレクトリに証明書が作成されるため、-vオプションによりホスト側のアプリ指定のディレクトリと共有させます。

certbotコマンドオプション
https://certbot.eff.org/docs/using.html#certbot-command-line-options


マニュアルモード(standalone + preferred-challenges)
https://certbot.eff.org/docs/using.html#manual

サーバ以外での用途にSSL認証を取得したい時に利用します。

ポート80を開放して以下コマンドを実行します。

$ sudo certbot certonly --standalone --preferred-challenges http -d test.example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for test.example.com
Waiting for verification...
Cleaning up challenges

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

/etc/letsencrypt/live/test.example.comに認証ファイルが作成されます。

$ sudo ls /etc/letsencrypt/live/test.example.com
README	cert.pem  chain.pem  fullchain.pem  privkey.pem

Nginx+Certbot Docker

Github

Docker hub
https://hub.docker.com/r/jonasal/nginx-certbot

リバースプロキシサーバ経由のサーバでLet’s Encryptによる証明書を取得

以下、同一LAN内に配置した②でLet’s Encryptによる証明書を取得する場合

WAN LAN Local IP
Internet >>> NAT(Router) >>> ① Nginx Reverse Proxy:192.168.0.10 >>> ② Nginx Server:192.168.0.20

注) ②はリバースプロキシサーバ①経由で配置したサーバ

①のマシン上でcertbotにより複数ドメインの証明書は取得できますが、②で証明書を他の用途(SIPサーバ等)でも使用したい場合、①からコピーするのではなく②でもcertbotにより取得します。

$ cetbot --nginx コマンド実行前の設定

① Nginx Reverse Proxyの設定 /etc/nginx/conf.d/your-domain.conf
注) プロキシ箇所のみの設定のため、別途default.confの設定も必要です。

server {
    server_name www.your-domain.com;

    server_tokens off;

    location / {
        proxy_pass http://192.168.0.20;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    listen 80;
}

② Nginx Severの設定 /etc/nginx/conf.d/default.conf

server {
    root /var/www/html;
    server_name www.your-domain.com;
    index ndex.html;

    location / {
          location ~ \.php$ {
             #fastcgi_pass fastcgi_backend;
             include fastcgi_params;
             fastcgi_param SCRIPT_FILENAME $request_filename;
             fastcgi_index index.php;
             fastcgi_pass 127.0.0.1:9000;
          }
    }

    listen 80;
}

Note) HTTP-01 challenge

②でcertbotコマンドを実行する前に①のproxy_passhttpであることを確認すること。

proxy_pass http://192.168.0.20;

②でcertbot実行後はhttpsにすること。

proxy_pass https://192.168.0.20;

HTTP-01 challenge

$ cetbot --nginx コマンド実行後の設定

① Nginx Reverse Proxyの設定 /etc/nginx/conf.d/your-domain.conf
注) プロキシ箇所のみの設定のため、別途default.confの設定も必要です。

server {
    server_name www.your-domain.com;

    server_tokens off;

    location / {
        proxy_pass https://192.168.0.20;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/www.your-domain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/www.your-domain.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = www.your-domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    server_name www.your-domain.com;
    listen 80;
    return 404; # managed by Certbot
}

② Nginx Severの設定 /etc/nginx/conf.d/default.conf

server {
    root /var/www/html;
    server_name www.your-domain.com;
    index ndex.html;

    location / {
          location ~ \.php$ {
             #fastcgi_pass fastcgi_backend;
             include fastcgi_params;
             fastcgi_param SCRIPT_FILENAME $request_filename;
             fastcgi_index index.php;
             fastcgi_pass 127.0.0.1:9000;
          }
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/www.your-domain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/www.your-domain.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

server {
    if ($host = www.your-domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    server_name www.your-domain.com;
    return 404; # managed by Certbot
}

Certbotコマンドオプション

https://certbot.eff.org/docs/using.html#certbot-command-line-options

一部抜粋

usage: 
  certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ...

Certbot can obtain and install HTTPS/TLS/SSL certificates.  By default,
it will attempt to use a webserver both for obtaining and installing the
certificate. The most common SUBCOMMANDS and flags are:

obtain, install, and renew certificates:
    (default) run   Obtain & install a certificate in your current webserver
    certonly        Obtain or renew a certificate, but do not install it
    renew           Renew all previously obtained certificates that are near expiry
    enhance         Add security enhancements to your existing configuration
   -d DOMAINS       Comma-separated list of domains to obtain a certificate for

  --apache          Use the Apache plugin for authentication & installation
  --standalone      Run a standalone webserver for authentication
  --nginx           Use the Nginx plugin for authentication & installation
  --webroot         Place files in a server's webroot folder for authentication
  --manual          Obtain certificates interactively, or using shell script hooks

   -n               Run non-interactively
  --test-cert       Obtain a test certificate from a staging server
  --dry-run         Test "renew" or "certonly" without saving any certificates to disk

manage certificates:
    certificates    Display information about certificates you have from Certbot
    revoke          Revoke a certificate (supply --cert-name or --cert-path)
    delete          Delete a certificate (supply --cert-name)

manage your account:
    register        Create an ACME account
    unregister      Deactivate an ACME account
    update_account  Update an ACME account
  --agree-tos       Agree to the ACME server's Subscriber Agreement
   -m EMAIL         Email address for important account notifications

Dockerfileでは --agree-tos オプションを使用

Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must agree in order to register with the ACME server. Do you agree?

(You can set this with the --agree-tos flag)

オプション '-n', '--agree-tos' を付与するとコマンド実行後のcertbotが要求する入力事項を省略できます。

$ certbot --nginx --agree-tos -n -d www.EXAMPLE.com -m EXAMPLE@gmail.com

Official Nginx DockerHubイメージ

NginxオフィシャルのDockerイメージでは、.template 拡張子を付けたテンプレート設定ファイル default001.conf.template の中で変数を定義することが出来ます。このテンプレートファイルはコンテナ起動時に、変数がdocker-composeファイルで指定した値や文字に変換されて /etc/nginx/conf.d フォルダに default001.conf ファイルとして保存され読み込まれます。

https://hub.docker.com/_/nginx

ex) default001.conf.template という設定ファイルのテンプレートを docker-compose.yml ファイルが格納されているフォルダ内に作成し、そのファイル内で変数 ${NGINX_HOST}, ${NGINX_PORT} を定義します。

./templates/default001.conf.template

server {
    root /var/www/html;
    server_name ${NGINX_HOST};
    listen ${NGINX_PORT};
    .....
    }
.....
.....

作成したテンプレートは、以下docker-composeファイルDockerコンテナ/etc/nginx/templates フォルダにコピーされます。nginxコンテナ起動時、変数が指定した値や文字に変換された設定ファイルが /etc/nginx/conf.d/default001.conf として読み込まれます。

web:
  image: nginx
  volumes:
   - ./templates:/etc/nginx/templates
  ports:
   - "8080:80"
  environment:
   - NGINX_HOST=foobar.com
   - NGINX_PORT=80

NginxコンテナへのCertbotの導入について

:bangbang:
Nginx公式イメージにCertbotを追加した新規イメージを作成し、コンテナ起動後にCertbotによる認証手続きを行うのではなく、事前にCertbotによるSSL認証手続きを専用のDockerイメージを利用してスタンドアローンモードで行い、取得した認証ファイルをdocker-composeファイル内で指定してNginxコンテナを起動することを推奨します。

Running on Docker
https://eff-certbot.readthedocs.io/en/stable/install.html#running-with-docker

$ sudo docker run -it --rm --name certbot -v "$PWD/letsencrypt:/etc/letsencrypt" -p 80:80 certbot/certbot certonly --standalone -d www.example.com

事前にSSL認証ファイルが取得できるため、Nginx443ポートを指定した設定ファイルを事前に用意出来ます。

更新については上の投稿記事 ”Certbotコマンドオプション” を参照してホストマシンのクローンジョブに更新コマンドを記述すること。

Certbot - ArchWiki

The factual accuracy of this article or section is disputed.
Reason: In the webroot way, the /var/lib/letsencrypt path is dictated by certbot.
Manual creation is not necessary, that applies to #Manual.

Nginx SSLオプションの適用

TLSバージョンの指定などのセキュリティ対策。TLSSSLの後継呼称として捉えて構いません。SIPなどのウェブアプリなどでTLS認証を使用する場合にはバージョンの整合性に注意。

nginx プラグイン --nginx を指定して取得した場合は下記ファイルが作成されるため、このファイル内で調整します。

/etc/letsencrypt/options-ssl-nginx.conf

TLSバージョンv1.2とv1.3を指定

# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.

ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";

ssl_prefer_server_ciphers on の場合
StandaloneモードでTLS認証を取得した場合は、openssl コマンドで以下の dhp-4096.pem ファイルを作成、格納ディレクトリを指定して下さい。(nginx プラグイン --nginx を指定して取得した場合、既に /etc/letsencrypt ディレクトリに格納されています。)

ssl_prefer_server_ciphers on;
  # on this container, "mkdir -p /etc/nginx/ssl"
  # on host machine, "sudo openssl dhparam -out dhp-4096.pem 4096"
  ssl_dhparam /etc/nginx/ssl/dhp-4096.pem;

OpenSSL Wiki バイナリ
Binaries - OpenSSLWiki

Certonlyモードでは上記暗号化に必要なファイルは自分で用意します。

以下のMozillaによるSSL Configuration Generatorも参考にして下さい。

IPv6アドレスによるSSL認証

ドメインのIPアドレスがIPv4とIPv6で設定されている場合、CertbotによるSSL認証ではIPv6アドレスが優先されて認証アクセスされます。

Domain Validation

When making outbound domain validation requests for a domain that has both IPv4 and IPv6 addresses (e.g. both A and AAAA records) Let’s Encrypt will always prefer the IPv6 addresses for the initial connection. If the IPv6 connection fails at the network level (e.g. there is a timeout) and there are IPv4 addresses available then we will retry the request with one of the IPv4 addresses.