work.log

元エンジニアの備忘録的ブログ

NginxでLet’s Encryptを使うためのメモ書き

投稿:2017-01-21 13:39  更新:

無料でSSL証明書を取得できるサービス Let’s Encrypt のメモです。

今回は CentOS + Nginx の環境で Let’s Encrypt のセットアップ、証明書インストール、自動更新設定をやっていきます。

スポンサーリンク

certbotのセットアップ

Let’s Encrypt が発行する証明書は有効期限が90日となっています。

このままだと運用が困るのでcertbotを使い自動更新出来るようにします。

以前はcertbot-autoというツールを使っていたのですが、これが現在は新規で使えなくなったので注意です。

また、セットアップ済みのcertbot-autoは現時点(2021/8/16)でも一応動き、SSL証明書も更新されていますがいずれ使えなくなるかもしれないので早めに対応した方がいいです。

久しぶりにcertbot-autoを新規インストールしたらうんともすんとも言わなくて衝撃。

なお、公式ではsnapdの利用が推奨されていますが、CentOS7だとこんなエラーが出て動かなかった。

# snap install certbot
error: system does not fully support snapd: cannot mount squashfs image using "squashfs": mount:
       /tmp/sanity-squashfs-058540335: failed to setup loop device: No such file or directory

調べてもイマイチわからないし、certbotの為にまた別なパッケージ管理アプリケーションを入れるのも何だかな…

CentOSも例のゴタゴタで8は2021年12月31日にサポートが切れるし、移行先のOSもまだ有力候補がある位なので今は2024年6月30日までサポートされるCentOS7のまましのぎたいところです。

そして何れ放棄するとわかっているものにこれ以上手間を掛けたくはありません。

また、既にサポートが切れたCentOS6も一部あるため、今回は両方で使える「PyenvでPython3系を使う」環境で対処する事にしました。

普段からPythonを使っている人ならこれでcertbotを管理するのが楽だと思います。

pyenvのインストール

まずはpyenvをインストール。root権限でこんな感じにやってます。

# cd /usr/local/ ; git clone git://github.com/yyuu/pyenv.git ./pyenv
# mkdir -p ./pyenv/versions ./pyenv/shims ; cd pyenv/plugins/
# git clone git://github.com/yyuu/pyenv-virtualenv.git
# git clone git://github.com/yyuu/pyenv-update.git
# echo 'export PYENV_ROOT="/usr/local/pyenv"' >> /etc/profile.d/pyenv.sh
# echo 'export PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${PATH}"' >> /etc/profile.d/pyenv.sh
# source /etc/profile.d/pyenv.sh

Python3とcertbotのインストール

次にPython3をpyenvでインストールし、certbotをインストールしていきます。

# cd /root
# pyenv install -v 3.6.9
# pyenv virtualenv 3.6.9 certbot-3.6.9
# mkdir py36 ; cd py36
# pyenv local certbot-3.6.9
# pip install --upgrade pip
# pip install certbot
動くかを確認
# certbot
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Certbot doesn't know how to automatically configure the web server on this system. However, it can still get a certificate for you. Please run "certbot certonly" to do so. You'll need to manually configure your web server to use the resulting certificate.

これでcertbotが使えるようになりました。

ただし、virtualenv環境にインストールしているのでpy36ディレクトリを抜けるとパスが通りません。

手動実行する場合はvirtualenvを設定したディレクトリに移動してやればいいと思いますが、crontabに設定する時は実体を指定してあげるといいです。

yumが既に使えないCentOS6ならpyenv globalでもいいかも知れないですが、localにしておいた方が他に影響を与えないので安心っちゃ安心です。

上記の場合だと下記にインストールされています。

/usr/local/pyenv/versions/certbot-3.6.9/bin/certbot

移行OSが決まったらCentOS7も捨てる事になるので、暫定処置としてはこれで十分です。

certbot-autoのアンインストール

certbot-autoを使っていた場合は、新しくインストールしたcertbotに置き換えます。

置き換えて問題ないようなら下記のように削除します。

# rm /usr/local/bin/certbot-auto

NginxでLet’s Encryptを使う準備

certbot は Web サーバを無停止で証明書を取得する Webroot プラグイン を利用するので、Nginx に Let’s Encrypt 用の設定を追加します。

certbot を実行するとツールがドメイン名の所有権を確認する認証ファイルを Web 公開用ディレクトリに作成し、それに対し Let’s Encrypt 側が HTTP とか HTTPS でアクセスしてくるのでこれを Nginx で通してあげる必要があります。

認証ファイルを設置するディレクトリは /home/www/letsencrypt として設定するので、mkdir でディレクトリを作っておきます。

以降で Nginx 側の設定例を書いていきますが、Basic 認証や負荷分散構成等の設定例も合わせて記載します。

ドメイン名を1台のWebサーバだけで使っているケース

まず最初は一般的な例です。大大はそのドメイン名を 1 台の Web サーバだけで使っていると思うので、その場合はこちらで大丈夫です。

server {

	listen       80;
	server_name  worklog.be;
	root         /home/blog/worklog.be/htdocs;

	access_log  /home/blog/worklog.be/log/nginx-image-access.log combined;
	error_log   /home/blog/worklog.be/log/nginx-image-error.log warn;

	index  index.html;

	location ^~ /.well-known/acme-challenge/ {
		root /home/www/letsencrypt;
	}

	location = /.well-known/acme-challenge/ {
		return 404;
	}

}

認証用アクセスの場合は Document Root を変更するように設定しています。

Basic認証を利用しているケース

Basic認証を設定しているドメイン名は下記のように、認証用ファイルへのアクセスはBasic認証の対象外とする必要があります。

server {

	listen       80;
	server_name  worklog.be;
	root         /home/blog/worklog.be/htdocs;

	access_log  /home/blog/worklog.be/log/nginx-image-access.log combined;
	error_log   /home/blog/worklog.be/log/nginx-image-error.log warn;

	index  index.html;

	location ^~ /.well-known/acme-challenge/ {
		root /home/www/letsencrypt;
	}

	location = /.well-known/acme-challenge/ {
		return 404;
	}

	location / {
		auth_basic "Please enter your ID and password";
		auth_basic_user_file /home/blog/worklog.be/htdocs/.htpasswd;
	}

}

ドメイン名を複数台のWebサーバで使っているケース

次は複数台環境でドメイン名を使っている場合です。

分散構成だと Let’s Encrypt の認証アクセスがどのサーバに着信するかわからないので、これを Nginx のロードバランス機能を使って処理します。

例えば、”192.168.0.1 ~ 192.168.0.3″ の3台で DNS ラウンドロビンをしている場合はこの様に設定します。

upstream acme-challenge {
	server 192.168.0.1:80;
	server 192.168.0.2:80;
	server 192.168.0.3:80;
}

server {

	listen       80;
	server_name  roundrobin.worklog.be;
	root         /home/blog/roundrobin.worklog.be/htdocs;

	access_log  /home/blog/roundrobin.worklog.be/log/nginx-image-access.log combined;
	error_log   /home/blog/roundrobin.worklog.be/log/nginx-image-error.log warn;

	index  index.html;

	location ^~ /.well-known/acme-challenge/ {
		root /home/www/letsencrypt;
		try_files $uri @acme-challenge;
	}

	location @acme-challenge {
		proxy_set_header Connection "";
		proxy_set_header HOST $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_pass http://acme-challenge;
		proxy_next_upstream http_404;
		proxy_intercept_errors on;
	}

}

これを全台に設定しておけば、certbot を実行したサーバを探してアクセスを振り分ける事ができます。

証明書の取得

次に certbot-auto を実行して証明書を取得します。

certbot \
certonly \
--webroot \
--agree-tos \
-w /home/www/letsencrypt \
-m ssl@example.com \
-d worklog.be

オプションの意味はこんな感じ。

certonly = SSL証明書の取得のみ
–webroot = 認証用ファイルを指定されたディレクトリに生成する。(.well-known ディレクトリ)
–agree-tos = 利用規約に同意する
-w = Document Root のパス、Let’s Encrypt の Bot がアクセスできる場所を指定する
-m = 連絡用のメールアドレス。Let’s Encrypt からのお知らせ等が届く
-d = 利用するドメイン名

問題が無ければ下記に証明書が作成されます。

# ls /etc/letsencrypt/live/worklog.be/
.  ..  cert.pem  chain.pem  fullchain.pem  privkey.pem  README

certbot は一度に複数のドメイン名で証明書を取得する事もできます。

certbot \
certonly \
--webroot \
--agree-tos \
-w /home/www/letsencrypt \
-m ssl@example.com \
-d worklog.be \
-d www.worklog.be \
-d blog.worklog.be

この場合は最初に指定したドメイン名 “worklog.be” で証明書ファイルが作成されます。

証明書をNginxにインストール

次に Let’s Encrypt の証明書を Nginx にインストールします。

Nginx で SSL を利用するには “–with-http_ssl_module” 付きでコンパイルしてインストールしておく必要があるのでお忘れなく。

まずは DH 鍵交換用の鍵を作っておきます。(環境によっては結構時間がかかる)

# mkdir /etc/nginx/ssl
# cd /etc/nginx/ssl
# openssl dhparam 2048 -out dhparam.pem

そして、対象のドメイン名でこんな感じに設定をします。

ドメイン名を1台のWebサーバだけで使っているケース

server {
	listen       80;
	server_name  worklog.be;
	return 301 https://worklog.be$request_uri;
}

server {

	listen       443 ssl;
	server_name  worklog.be;
	root         /home/blog/worklog.be/htdocs;

	access_log  /home/blog/worklog.be/log/nginx-access.log combined;
	error_log   /home/blog/worklog.be/log/nginx-error.log warn;

	ssl                        on;
	ssl_protocols              TLSv1.2 TLSv1.3;
	ssl_prefer_server_ciphers  on;
	ssl_ciphers                'ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!aNULL!eNull:!EXPORT:!DES:!3DES:!MD5:!DSS';
	ssl_dhparam                /etc/nginx/ssl/dhparam.pem;
	ssl_certificate            /etc/letsencrypt/live/worklog.be/fullchain.pem;
	ssl_certificate_key        /etc/letsencrypt/live/worklog.be/privkey.pem;

	index  index.html;

	location ^~ /.well-known/acme-challenge/ {
		root /home/www/letsencrypt;
	}

	location = /.well-known/acme-challenge/ {
		return 404;
	}

}

Basic認証を利用しているケース

Basic認証を利用している場合は取得時の設定と同じように、”/.well-known/acme-challenge/” 以下をBasic認証の対象外としておく。

server {
	listen       80;
	server_name  worklog.be;
	return 301 https://worklog.be$request_uri;
}

server {

	listen       443 ssl;
	server_name  worklog.be;
	root         /home/blog/worklog.be/htdocs;

	access_log  /home/blog/worklog.be/log/nginx-access.log combined;
	error_log   /home/blog/worklog.be/log/nginx-error.log warn;

	ssl                        on;
	ssl_protocols              TLSv1.2 TLSv1.3;
	ssl_prefer_server_ciphers  on;
	ssl_ciphers                'ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!aNULL!eNull:!EXPORT:!DES:!3DES:!MD5:!DSS';
	ssl_dhparam                /etc/nginx/ssl/dhparam.pem;
	ssl_certificate            /etc/letsencrypt/live/worklog.be/fullchain.pem;
	ssl_certificate_key        /etc/letsencrypt/live/worklog.be/privkey.pem;

	index  index.html;

	location ^~ /.well-known/acme-challenge/ {
		root /home/www/letsencrypt;
	}

	location = /.well-known/acme-challenge/ {
		return 404;
	}

	location / {
		auth_basic "Please enter your ID and password";
		auth_basic_user_file /home/blog/worklog.be/htdocs/.htpasswd;
	}

}

ドメイン名を複数台のWebサーバで使っているケース

upstream acme-challenge {
	server 192.168.0.1:443;
	server 192.168.0.2:443;
	server 192.168.0.3:443;
}

server {
	listen       80;
	server_name  roundrobin.worklog.be;
	return 301 https://roundrobin.worklog.be$request_uri;
}

server {

	listen       443 ssl;
	server_name  roundrobin.worklog.be;
	root         /home/blog/roundrobin.worklog.be/htdocs;

	access_log  /home/blog/roundrobin.worklog.be/log/nginx-image-access.log combined;
	error_log   /home/blog/roundrobin.worklog.be/log/nginx-image-error.log warn;

	index  index.html;

	location ^~ /.well-known/acme-challenge/ {
		root /home/www/letsencrypt;
		try_files $uri @acme-challenge;
	}

	location @acme-challenge {
		proxy_set_header Connection "";
		proxy_set_header HOST $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_pass https://acme-challenge;
		proxy_next_upstream http_404;
		proxy_intercept_errors on;
	}

}

SSL 化後も更新の度に Let’s Encrypt の認証アクセスが来るので、その設定を残しつつハイライト表示にしている箇所を “HTTP => HTTPS” に変更します。

注意事項 Androidで起きる問題

余談ですが、このように証明書を指定してしまうと Android で証明書エラーが出るので、”ssl_certificate” には中間証明書とサーバ証明書が一緒になった “fullchain.pem” を必ず指定した方が良いです。

	## Android だけで起こる NG な例
	ssl_dhparam                /etc/nginx/ssl/dhparam.pem;
	ssl_certificate            /etc/letsencrypt/live/worklog.be/cert.pem;
	ssl_certificate_key        /etc/letsencrypt/live/worklog.be/privkey.pem;
	ssl_trusted_certificate    /etc/letsencrypt/live/worklog.be/chain.pem;

Android の実機はたまに動作確認用でしか使わないから暫くの間エラーを出していました…

後はコンフィグチェックをして問題が無ければリロードすれば無事 SSL 化されます。

# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

# service nginx reload

対象ドメイン名にブラウザからアクセスして SSL が有効 & Let’s Encrypt の証明書が使われていればOKです。

Let’s Encryptの自動更新設定

最後は証明書を自動更新するように cron に設定を仕込みます。

■ CentOS 6

# crontab -e
----- cron ------
0 10 * * * /usr/local/pyenv/versions/certbot-3.6.9/bin/certbot renew -q --post-hook "/sbin/service nginx reload" > /dev/null 2>&1

■ CentOS 7

# crontab -e
----- cron ------
0 10 * * * /usr/local/pyenv/versions/certbot-3.6.9/bin/certbot renew -q --post-hook "systemctl reload nginx" > /dev/null 2>&1

CentOS6 と CentOS7 でデーモンの起動方法が変わっているので間違えないように注意。

Let’s Encrypt の SSL 証明書は「毎日 10:00」にチェック&証明書を更新をするように cron を設定しました。何時に更新するかは好みですが、Nginx のサービス断は無いのでいつやってもいいと思います。

オプションの renew は有効期限を確認して対象なら更新。–post-hook は更新後に1回だけ実行されるフックで、更新があった場合のみ Nginx をリロードします。

これで煩わしい証明書の更新作業をしなくて良くなったので楽になりました。

証明書にドメインを追加したい場合

サブドメインを同じ証明書に追加したい場合は –expand オプションを付けて下記のように実行します。

※ 元のドメイン worklog.be、追加したいドメイン sub.worklog.be

certbot \
certonly \
--webroot \
--agree-tos \
--expand \
-w /home/www/letsencrypt \
-d worklog.be \
-d sub.worklog.be

証明書を失効&削除したい場合

証明書を失効させ削除したい場合は証明書ファイルを指定して下記のように実行します。

# certbot revoke --cert-path=/etc/letsencrypt/live/<コモンネーム>/cert.pem
# certbot revoke --cert-path=/etc/letsencrypt/live/worklog.be/cert.pem
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you like to delete the cert(s) you just revoked?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es (recommended)/(N)o: Y <= "Y" を入力してエンター

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Deleted all files relating to certificate worklog.be.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully revoked the certificate that was located
at /etc/letsencrypt/live/worklog.be/cert.pem

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

これで関連ファイルが letsencrypt 内のディレクトリから削除されます。

あとは Nginx の設定を修正して reload しておけば OK です。

自動更新に失敗するとメールが届く

自動更新に失敗していると期限切れ前にこのような通知メールが届きます。無料なのに親切です。

Subject: Let’s Encrypt certificate expiration notice
From: Let’s Encrypt Expiry Bot <expiry@letsencrypt.org>

Hello,

Your certificate (or certificates) for the names listed below will expire in
9 days (on 21 Apr 17 14:00 +0000). Please make sure to renew
your certificate before then, or visitors to your website will encounter errors.

worklog.be

(以下略)

という事で、今後新しくサイトを立ち上げる場合は Let’s Encrypt で積極的に SSL 化していくと良いと思います。

スポンサーリンク

コメント

  1. 匿名 より:

    勉強なりました

  2. 匿名 より:

    自分でサイトを作っている時にすごく参考になりました。
    ありがとうございます

コメントを残す

よく読まれている記事

  • 本日
  • 週間
  • 月間