はじめに
前回の記事に引き続き、今回は人気の高性能ウェブサーバであるNginxの導入と設定、そしてセキュアなHTTPSを構築する方法について説明していきます。
シリーズは今回で第二回となります。
これまでに公開した当シリーズに関する記事は以下のとおりです。
- Ubuntu20.04のInstallおよびSSH、FTP設定【第一回】
- Nginxの導入および設定&セキュアなHTTPS化【第二回】
- https://www.pr1sm.com/web/nginx-install-and-https-settings/
- Rust製SSG、Zolaの導入および各種設定【第三回】
- Nginxリバースプロキシで静的コンテンツ専用サーバを作成【第四回】
- WordPress&MariaDBの導入および高速化設定【第五回】
- サイトの安全性を更に高めるWordPressセキュリティ設定【第六回】
なお、このシリーズではホスティングにVultrを使っています。
- Vultr
サーバのスペックおよび構成は以下の通りです。
$ cat /etc/lsb-release
- DISTRIB_ID=Ubuntu
- DISTRIB_RELEASE=20.04
- DISTRIB_CODENAME=focal
- DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"
Vultr : High Frequency Server
- 32GB MVMe
- 1CPU
- 1GB Memory
- Additional Features
- Virtual Private Clouds: ON
Nginxの導入
このセクションではNginxの導入手順について説明します。
IPアドレス直打ちによるサイト表示ではなく、自身で用意したドメインを使う場合には、事前にドメインに対するDNS(Domain Name System)の設定を行っておいてください。
テスト用のドメインを取得したい場合、Freenomを使えば無料でドメインを取得することができます(ユーザ情報をある程度埋めないと取得時にエラーが出るので注意)。
- Freenom
なお、このまま普通にインストールを行うとUbuntuで用意された古いバージョンのNginxがインストールされてしまうため、リポジトリ(ファイルの収納庫)を追加する必要があります。また、安全のために署名鍵検証も行う必要があります。 Nginxのインストール準備が整いましたので そして インストール後、 これまでのシリーズではUFWのポートは基本的に閉鎖しているため、ウェブサーバに必要な80番ポートを インストール直後ではNginxは起動していないため、 この状態でサーバのIPアドレスにアクセスしてみましょう。Welcome to nginx!というページを表示できれば、無事Nginxの導入が完了です。署名鍵のインポート
$ curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
/usr/share/keyrings/nginx-archive-keyring.gpg
に格納されました。リポジトリの追加
$ sudo vim /etc/apt/sources.list.d/nginx.list
でNginx用のリストを作成します。nginx.list
## Replace $release with your corresponding Ubuntu release.
# deb https://nginx.org/packages/ubuntu/ $release nginx
# deb-src https://nginx.org/packages/ubuntu/ $release nginx
# Ubuntuのリリース名については`$ cat /etc/lsb-release`で確認することができます。
# Ubuntu20.04では$releaseの箇所がfocalとなる
deb [arch=amd64 signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://nginx.org/packages/ubuntu/ focal nginx
deb-src [arch=amd64 signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://nginx.org/packages/ubuntu/ focal nginx
Nginxのインストールおよび起動
$ sudo apt update
でファイルのアップデートをチェックします。$ sudo apt install nginx
でNginxのインストールを行います。$ nginx -v
で現在のバージョンをチェックできます(執筆時点ではstableで1.20.2)。$ sudo ufw allow 80
で開放します。$ sudo systemctl start nginx
でNginxを起動させます。$ systemctl status nginx
でActiveの箇所がactive (running)
となっていればNginxは起動状態となっています。
Nginx設定
では各種Nginxの設定を行っていきます。
例としてNginxが参照するウェブサイトのルートは
/var/www/ドメイン名
として扱います(example.comであれば/var/www/example.com)。そのため、$ sudo mkdir -p /var/www/ドメイン名
でディレクトリを作成しておきます。そして、
$ sudo cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/ドメイン名.conf
でNginxのサイト用設定ファイルを自身のサイト用としてコピーします。その後
$ sudo mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf_b
というように、使わないデフォルトファイルをリネームしておきます。$ sudo vim /etc/nginx/nginx.conf
でNginxの全体設定を行うファイルを編集します。
nginx.conf
#### nginx documentation
#### https://nginx.org/en/docs/
# workerプロセスの実行ユーザを指定
user nginx;
# workerプロセスの数(CPUの数)を指定
worker_processes auto;
# CPUの使用を均等にする
worker_cpu_affinity auto;
# workerプロセスの最大オープンファイル数の上限(RLIMIT_NOFILE)を変更
# workerプロセス数 * 設定する予定のworker_rlimit_nofileの値 < OS全体で扱えるファイル数以下の値
# OS全体で扱えるファイル数 / workerプロセス数 = 設定すべきworker_rlimit_nofileの値
#
# OSが扱える最大ファイル数
# $ cat /proc/sys/fs/file-max
#
# OS全体で扱えるファイル数 / workerプロセス数 = 設定すべきworker_rlimit_nofileの値(上限ギリギリにはしない)
#
# 余裕を持たせる場合はworker_connections の2-4倍以上の値を設定
# [Nginx]worker_connectionsとworker_rlimit_nofileの値は何がいいのか?
# https://qiita.com/mikene_koko/items/85fbe6a342f89bf53e89
worker_rlimit_nofile 100000;
# nginxのプロセスID格納先を指定
pid /var/run/nginx.pid;
# PCRE JITを用いて正規表現の処理を高速化させる
pcre_jit on;
events {
# 1つのworkerプロセスが処理する最大コネクション数を指定
# 基本的に"worker_connections * 2 < worker_rlimit_nofile"を守る
worker_connections 1024;
# クライアントからのリクエストをできるだけ受け取る
multi_accept on;
# Linuxカーネル2.6以上の場合はepoll、BSDの場合kqueue
# epollはselect/pollに比べて計算量が少なく、また監視対象のファイルディスクリプタの数が無制限
use epoll;
# workerプロセスが順番に新しい接続を受け入れる(接続量が少ない場合はリソースが無駄なので"off")
accept_mutex on;
# mutexの確保に失敗した際の待機時間を調整
accept_mutex_delay 100ms;
}
http {
# HTTPレスポンスヘッダのContent_Typeに付与する文字コード
charset UTF-8;
# nginxがデフォルトで用意するMIMEタイプと拡張子のマッピングファイル
include /etc/nginx/mime.types;
# マッピングにない拡張子のdefault
default_type application/octet-stream;
# ログフォーマット。レスポンスタイプ「$request_time」を追加
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$request_time"';
# 設定したアクセス数に対して制限を定義する(ドメイン名.confにも設定)
# ファイルリクエスト1件ごとに判定が行われるので注意
# Module ngx_http_limit_req_module
# https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone
# 20/sを超えるリクエストを行ったIPを制限ゾーンに配置する
# limit_req_zone $binary_remote_addr zone=one:10m rate=20r/s;
# 制限ゾーンに入ったIPは10reqを限度に処理を待機(超えると即エラー)
# limit_req zone=one burst=10 nodelay;
# limit_reqに関するログの発生レベルをerrorに設定
# limit_req_log_level error;
# 制限時のステータスを444で返却する
# limit_req_status 444;
# Nginxでログの出力を停止する
# https://worklog.be/archives/2890
# エラーログを出力しない
#error_log /dev/null crit;
# エラーログのレベルをwarnに設定
error_log /var/log/nginx/error.log warn;
# アクセスログの出力を停止する
#access_log /var/log/nginx/access.log main;
access_log off;
# 毎回リクエストが飛ぶのでサーバ側でキャッシュ制御を行わない
etag off;
# nginxのバージョンを非表示
server_tokens off;
# ハードディスクio処理とsocket-io処理のバランスを取る
sendfile on;
# 一つのデータパッケージに全てのヘッダー情報を含む
tcp_nopush on;
# 送信済みデータの応答待ちの状態でも遅延させることなくデータ送信 負荷が高い場合はoff
tcp_nodelay on;
# HTTP通信をタイムアウトせずにいる秒数
keepalive_timeout 10;
# クライアントのリクエストヘッダに対する読み込みのタイムアウト時間
client_header_timeout 10;
# クライアントのリクエストボディに対する読み込みタイムアウト時間
client_body_timeout 10;
# タイムアウトした接続をリセットするかどうかの設定
reset_timedout_connection on;
# クライアントに応答を返すまでのタイムアウト時間
send_timeout 10;
# server_name が長い場合に発生するエラーへの対処
server_names_hash_bucket_size 64;
# コンテンツ配信などの最適化に利用するハッシュテーブルのサイズを設定
types_hash_max_size 1024;
# 同時接続数制限の処理で確保するメモリサイズ
limit_conn_zone $binary_remote_addr zone=addr:10m;
# 1つのIPアドレスからの接続数の制限値を設定
limit_conn addr 100;
# レスポンスをgzipで圧縮する
gzip on;
# レスポンスヘッダに Vary: Accept-Encoding を挿入
gzip_vary on;
# HTTPバージョン1.0から圧縮
gzip_http_version 1.0;
# IE6では圧縮されたファイルの展開に失敗することがあるので圧縮しない
gzip_disable "msie6";
# Viaヘッダが付いててもレスポンスをgzip圧縮
# コンテンツキャッシュとVaryヘッダとnginx
# https://qiita.com/cubicdaiya/items/09c8f23891bfc07b14d3
gzip_proxied any;
# 圧縮対象となるサイズを一定以上にすることでCPU負荷を軽減
gzip_min_length 1024;
# gzip圧縮サイズは1と9で大差ないので1を設定
gzip_comp_level 1;
# gzip圧縮で使用するバッファサイズ
gzip_buffers 16 8k;
# 指定されたMIMEタイプを圧縮する
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
# 事前にgz圧縮されたファイルがあればgzをそのまま相手に送る
gzip_static on;
# gzipに対応していないクライアントの場合、gzを解凍してから相手に送る
gunzip on;
# ファイルディスクリプタやサイズ、更新時間などのメタデータをキャッシュする最大数と非アクセス時の削除時間
open_file_cache max=1000 inactive=10s;
# キャッシュのチェック間隔
open_file_cache_valid 60s;
# 設定された回数使われていなければ、キャッシュされない
open_file_cache_min_uses 1;
# open_file_cacheによるファイルルックアップエラーのキャッシュの有無
open_file_cache_errors on;
# 各種confを読み込む
include /etc/nginx/conf.d/*.conf;
}
- 次に
$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
でサイト側にのみ適用する設定を行います。
ドメイン名.conf
server {
# 接続するポート番号を指定
listen 80;
# URLに表示されるドメインまたはIPアドレス
# server_name localhost;
# server_name example.com;
server_name ドメイン名;
# ウェブサイト表示時に参照されるディレクトリ
root /var/www/ドメイン名;
# indexページとして読み込むファイル(indexは各locationにも継承される)
index index.php index.html index.htm;
# 初期位置
location / {
}
# 404ページの指定
#error_page 404 /404.html;
# location = /404.html {
#}
# 50*ページの指定
error_page 500 502 503 504 /50x.html;
location = /50x.html {
# 50x.htmlを参照するルートを指定
root /usr/share/nginx/html;
}
}
$ sudo service nginx restart
を実行することでこれまでの設定が反映されます。しかし、現在の設定では
/var/www/ドメイン名
に何もファイルが置いていない状態なので、Nginxは403ページを表示します。そのため、index.html
を該当ルートに直接あるいはFTP経由などで設置すると、エラーが発生することがなくなります。
HTTPSに対応させる
問題なくウェブサーバおよびサイトが動作するならば、HTTPSはとても簡単に導入することができます。
また、何らかのドメインを所有していればLocalhost(ローカルホスト)でのHTTPS環境を作ることも可能です(先述したFreenomを使えば無料)。
localhostの場合は取得したドメインのDNSサーバ設定でAレコードのVALUE(サブドメインを入力する箇所ではない)に127.0.0.1を設定しておいてください。
今回の記事ではLet's Encryptと呼ばれる、無料で証明書が取得可能な認証局を使用してHTTPS化を行います。 HTTPSで通信を行うには まず 次に 旧バージョンのcertbotが入っていれば CertbotのインストールにSnap版を使うようになった理由は以下に記載されています。 インストールが終われば、 実行後は連続して文章が出てきますので、その内容に沿ってタイプしていきます。 SnapでCertbotのインストールを行った場合は証明書の自動更新に対応しています。 次の更新が正しく行われるかどうかは 取得した証明書が格納されている場所は以下のとおりです。これは方法を問わず共通です。 なお、 通常方法での証明書取得は非常に手軽ですが、ワイルドカードおよびlocalhostの場合はDNSを用いてチェックを行わなければなりません。 DNS認証を用いた証明書取得ではTXTレコードを毎回更新する必要があることから、通常の自動更新が利用できません。 つまり、手動で更新しなければならないので非常に手間がかかります。 スクリプトを組んで自動更新する方法もありますが、利用するサービスによって手法が異なります。 上記のような自動更新を使う場合には、ドメインを取得したサイトで適用されているDNSネームサーバを、自動更新に使うサービスのネームサーバへ移動させる必要があります。 例えばFreenomでドメインを取得したケースで考えると、Freenomの ただし執筆時点では、Freenomで取得したドメイン(.cf, .ga, .gq, .ml, .tk)はCloudflareやClouDNSなどの無料で使えるDNSサービスからは弾かれています(本番環境で無料ドメインを使うケースは少ないとは思いますが)。 しかしながら、certbot-dns-freenomというPython製のプラグインを使用することで自動更新を行うことは可能です(Cloudflare版は後述)。 以前Certbotをインストールしていた場合は競合による不具合を避けるため、 snapでもCertbotをインストールしていた場合には Certbotにシンボリックリンクを設定していた場合には Certbot用のPython仮想環境を構築するために それが終われば その後 インストールが完了したら ここまで来ると通常のCertbotコマンドは使用可能になっていますが、FreenomのDNS認証用プラグインが必要なので、 あとは コマンドを実行中、DNSの反映を待つために 無事証明書の発行に成功したら、あとは自動更新の設定を行うだけです。 上記コマンドでは、毎月2日と17日の午前3時に証明書の更新を行うようにしています。 証明書取得時、 このセクションではCloudflareを用いた無料自動更新の手順を紹介します。こちらの方法では有料ドメインの場合でのみ対応可能です(もともとはFreenomの無料ドメインをこちらを使う予定でしたが、最後の最後にエラーが発生して対応していない事に気付きました)。 Cloudflareのアカウントは既に登録してあることを前提として進めていきます ログイン後、Cloudflareでサイトを何も追加していなければ その後、 変更後は長くて24時間かかる場合がありますので、気長に待ってください。 反映をチェックできたら、次は 自動更新を行うにはCloudflareのAPI(Application Programming Interface)から キーを入手したら APIキーは重要な情報なので、 一通りの作業が終わりましたので、以下のコマンドを実行します。こちらも少し長いので折り返しコマンド( 無事テストが完了したら、 このままでは自動更新とはならないため、 Freenomの時と同じコマンドを用いています。 同じ説明を以下に掲載します。LetsEncryptのインストールおよび証明書の取得
443
ポートが必要なので$ sudo ufw allow 443
でUFWに許可させておきます。$ sudo apt update
でアップデートチェックを行った後、$ sudo apt install snapd
でSnappyと呼ばれるパッケージ管理システムをインストールします(最近のUbuntuであれば基本的に既に入っています)。$ sudo snap install core
および$ sudo snap refresh core
で最新バージョンのsnapを使用しているか確認します。$ sudo snap install --classic certbot
でsnapのセキュリティ制限を回避(classic)しつつCertbotをインストールします。$ sudo apt remove certbot
で証明書等のファイルを残したままcertbotを削除できます。$ sudo apt install certbot
でcertbotをインストールする方法もありますが、バージョンが古すぎる(0.40)ので推奨されません。$ sudo certbot --nginx -d ドメイン名
でCertbotを用いて証明書の取得を行います。$ sudo certbot --nginx -d example.com -d www.example.com
というコマンドならば、www有り無し両方を取得できます。1. 証明書の更新やセキュリティに関するメールを受信するためのメールアドレスを入力する。
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
(Enter 'c' to cancel): メールアドレスを入力
2. 規約に同意するかどうか同意(Yes)しないと次に進めない。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
3. メールアドレスをElectronic Frontier財団に登録するかどうか。
Yes(Y)で登録するとメールマガジンが配信される。
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
(Y)es/(N)o: Y
4. 問題無く証明書を取得できれば、証明書の場所とその有効期限が表示されます。
Account registered.
Requesting a certificate for 証明書を取得するドメイン名
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2022-01-01-.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Deploying certificate
Successfully deployed certificate for 証明書を取得するドメイン名 to /etc/nginx/conf.d/ドメイン名.conf
Congratulations! You have successfully enabled HTTPS on https://ドメイン名
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$ view /etc/systemd/system/snap.certbot.renew.timer
および$ view /etc/systemd/system/snap.certbot.renew.service
で自動更新の日時をチェックすることができます。$ sudo certbot renew --dry-run
によって動作確認が可能です。Congratulations, all simulated renewals succeeded
が表示されていれば正常です。証明書と中間証明書を連結したファイル
/etc/letsencrypt/live/ドメイン名/fullchain.pem;
秘密鍵
/etc/letsencrypt/live/ドメイン名/privkey.pem;
中間証明書
/etc/letsencrypt/live/ドメイン名/chain.pem
証明書
/etc/letsencrypt/live/ドメイン名/cert.pem
/etc/letsencrypt/
内には役割の異なる複数のディレクトリが存在しています。/etc/letsencrypt/内のディレクトリ一覧
過去に取得した各証明書(cert,chain,fullchain,privkey)に番号+ドメインごとに格納(数字が多いもの程新しい)。
/etc/letsencrypt/archive/
例:/etc/letsencrypt/archive/ドメイン名/cert5.pem
archiveへのシンボリックリンクを行い、その中で最新の証明書にリンクさせている。
/etc/letsencrypt/live/
例:/etc/letsencrypt/live/ドメイン名/cert.pem(archive内の最新証明書)
証明書更新(renew)用の設定ファイルを格納している。
/etc/letsencrypt/renewal/
例:/etc/letsencrypt/renewal/ドメイン名.conf
ワイルドカード、localhostでの証明書取得
$ sudo certbot certonly --manual -d ドメイン名 --preferred-challenges dns-01
を使います。 $ sudo certbot certonly --manual -d ドメイン名 -d *.ドメイン名 --preferred-challenges dns-01
というようにサブドメインの部分にアスタリスク(*)を記述します。1. 証明書の更新やセキュリティに関するメールを受信するためのメールアドレスを入力する。
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
メールアドレスを入力
2. 規約に同意するかどうか Agreeしないと次に進めない。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
3. メールアドレスをElectronic Frontier財団に登録するかどうか。
Yes(Y)で登録するとメールマガジンが配信される。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
4. 表示される※ランダムな文字列※を、ドメインのDNS設定でTXTレコードに保存します。
サブドメインの欄は _acme-challenge です。
※注意:TXTレコードがDNSに反映されていない時にEnterを押して進もうとした場合、
チェックが失敗するのでやり直しになります。
この場合、再度certbotを実行することにより、文字列が別物になってしまうことから、
再びTXTレコードに記入して待つ必要があります。
別のターミナルで"$ nslookup -type=txt _acme-challenge.ドメイン名 8.8.8.8"を実行すれば、
TXTレコードがDNSに反映されているかチェック可能です。
https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.ドメイン名 でチェックもOKです。
Account registered.
Requesting a certificate for ドメイン名
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:
_acme-challenge.ドメイン名
with the following value:
※ランダムな文字列※
Before continuing, verify the TXT record has been deployed. Depending on the DNS
provider, this may take some time, from a few seconds to multiple minutes. You can
check if it has finished deploying with aid of online tools, such as the Google
Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.ドメイン名
Look for one or more bolded line(s) below the line ';ANSWER'. It should show the
value(s) you've just added.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
5. 証明書の取得が完了する。
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/ドメイン名/fullchain.pem
Key is saved at: /etc/letsencrypt/live/ドメイン名/privkey.pem
This certificate expires on 2022-01-01.
These files will be updated when the certificate renews.
NEXT STEPS:
- This certificate will not be renewed automatically.
Autorenewal of --manual certificates requires the use of
an authentication hook script (--manual-auth-hook)
but one was not provided.
To renew this certificate, repeat this same certbot command before
the certificate's expiry date.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NEXT STEPS:
と書かれた注意書きが表示されており、この証明書は自動的には更新されないようになっています。理由や自動更新の方法については次の項目で説明します。DNS認証を用いた場合の自動更新(Freenom)
My Domains
からManage Domain
に移動し、Management Tools
内のNameservers
でUse custom nameservers (enter below)
を選んで移動したいDNSサービスのネームサーバを登録します。$ sudo apt remove certbot
で既存のcertbotをアンインストールします。remove
ではなくpurge
を使った場合は証明書ごと消えるので注意。$ sudo snap remove certbot
を実行してアンインストールします。$ whereis certbot
で場所を探し、$ sudo unlink リンクの場所
でリンクも削除します。$ sudo apt install python3.8-venv
でvenv(virtual environment)をインストールします。$ sudo python3 -m venv /opt/certbot/
で/opt/certbot/
上に仮想環境を構築します。$ sudo /opt/certbot/bin/pip install --upgrade pip
でpip(Pip Installs Packages)コマンドを最新にアップグレードしておきます。$ sudo /opt/certbot/bin/pip install certbot
でCertbotをインストールします。$ certbot --version
で現在のCertbotのバージョンをチェックできます。$ sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbot
によって/usr/bin/certbot
にシンボリックリンクを作成しておきましょう。$ sudo /opt/certbot/bin/pip install certbot-dns-freenom
を実行してインストールします。certbot-dns-freenom
ではfreenomにログインするための情報が必要となります。そのため、$ sudo vim /etc/letsencrypt/freenom_secret.ini
でログイン情報を記述しておきます。/etc/letsencrypt
ディレクトリが存在しない場合は$ sudo mkdir /etc/letsencrypt
でディレクトリを作成しておきましょう。freenom_secret.ini
dns_freenom_username = freenomに登録したメールアドレス
dns_freenom_password = 上記に対するfreenomのログインパスワード
freenom_secret.ini
はログイン情報が入っているため、非常に大切なファイルです。なので$ sudo chmod 600 /etc/letsencrypt/freenom_secret.ini
で権限を変更しておきましょう。certbot-dns-freenom
専用のコマンドを実行するだけですが、これは少々長いので折り返しコマンド(\
)で分けています(コピーすればまとめて実行可能)。--dry-run
によってテスト実行を行っているため、正常に完了したらそのコマンドを削除してもう一度実行してください。-d *.ドメイン名
というようにアスタリスク(*)を入れてください。$ sudo certbot certonly --dry-run -a dns-freenom \
--dns-freenom-credentials /etc/letsencrypt/freenom_secret.ini \
--dns-freenom-propagation-seconds 330 \
-d ドメイン名
dns-freenom-propagation-seconds
で330秒に設定しています(ただしFreenomでは変更が即座に反映されにくいため、場合によってはもっと秒数が必要になる場合もあります)。$ sudo vim /etc/crontab
に追記します。crontab
0 3 2.17 * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew -q" | sudo tee -a /etc/crontab > /dev/null
/etc/letsencrypt/renewal/ドメイン名.conf
に取得時の設定が記載されているため、# certbot renew
でも問題無く更新を行うことができます。DNS認証を用いた場合の自動更新(Cloudflare)
You currently don't have any websites.
の下にAdd site
というボタンがあるため、それを選択して取得したドメイン名を追加します。Select a plan
の項目が表示されますが、下部にFree $0
がありますのでそちらを選んでContinue
を選択します。Review your DNS records
の項目が出てくるので現在のDNSの構成が正しければContinue
を選んでください。Change your nameservers
ではネームサーバの変更を促されるため、ドメインを取得したサイト内にて現在のネームサーバからCloudflareが提供しているネームサーバへと変更を行います。$ nslookup -type=ns ドメイン名 8.8.8.8
で確認可能です。チェック時にCloudflareのネームサーバが表示されていれば反映されています。$ sudo apt-get install python3-certbot-dns-cloudflare
でDNSを自動更新するためのパッケージをインストールします。Global API Key
をView
することでキーを取得する必要があります。$ sudo vim /etc/letsencrypt/cloudflare_secret.ini
で必要情報を追記します。cloudflare_secret.ini
dns_cloudflare_email = Cloudflareに登録したメールアドレス
dns_cloudflare_api_key = 取得したGlobal API Key
$ sudo chmod 600 /etc/letsencrypt/cloudflare_secret.ini
で権限(パーミッション)を変更しておきましょう。\
)で分けています(コピーすればまとめて実行可能)。-d *.ドメイン名
も追加してください。$ sudo certbot certonly --dry-run --dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare_secret.ini \
--dns-cloudflare-propagation-seconds 60 -d ドメイン名
--dry-run
を外して証明書の取得を行うことが可能です。$ sudo vim /etc/crontab
で定期更新を行うための設定を行います。crontab
0 3 2.17 * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew -q" | sudo tee -a /etc/crontab > /dev/null
/etc/letsencrypt/renewal/ドメイン名.conf
に取得時の設定が記載されているため、# certbot renew
でも問題無く更新を行うことができます。
TLS設定
証明書を無事取得できたら、Nginxの設定ファイルを編集します。
今回は比較的安全性の高いTLS設定だけでなく、一部のセキュリティヘッダーも含めた、全体的にセキュアな構成を作成します。
$ sudo vim /etc/nginx/conf.d/ドメイン名.sh.conf
で新規に設定ファイルを編集します。- これは記述量が多くなるためにファイルを別にしているのであって、問題なければ
ドメイン名.conf
内にある、本体のServerディレクティブ内に記述しても大丈夫です(TLSを使うServerディレクションには証明書をそれぞれ配置してください)。 - 証明書の取得直後、
/etc/nginx/conf.d/ドメイン名.conf
内に# managed by Certbot
という文言が追加されている場合があります。その場合は一度それらをコメントアウトしてください(同じ項目が二重となるため)。 /etc/nginx/nginx.conf
でinclude /etc/nginx/conf.d/*.conf;
を設定しているため、Nginxでサイトを複数管理している場合は証明書等でエラーが発生します。その場合は/etc/nginx/conf.d/ドメイン名.conf
内に設定をすべて書き加えてもOKです。- レポートにはreport-uri.comを用いています。
- Report-URI
- これは記述量が多くなるためにファイルを別にしているのであって、問題なければ
ドメイン名.sh.conf
# 一部設定をコメントアウトしていますが、サイトごとに調整してください
#
# TLS設定
#
# 証明書の場所を指定
ssl_certificate /etc/letsencrypt/live/ドメイン名/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ドメイン名/privkey.pem;
# キャッシュをsharedのみ使用する(10メガバイト分)
ssl_session_cache shared:SSL:10m;
# タイムアウト時間を5分に指定
ssl_session_timeout 5m;
# 前方秘匿性のためにセッションのキャッシュを保持させない
ssl_session_tickets off;
# 使用するTLSのバージョンを指定
ssl_protocols TLSv1.2 TLSv1.3;
# DH鍵交換に使用するパラメータファイルの場所を指定
ssl_dhparam /etc/ssl/certs/dhparam.pem;
# 楕円曲線暗号の種類を指定
ssl_ecdh_curve secp384r1;
# サーバが対応する暗号化スイートを指定
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;
# TLS1.2からはクライアント側ハードウェアが優先暗号化方式を選択できるためoff
ssl_prefer_server_ciphers off;
# HSTSを常に有効化、期限を2年間に指定、サブドメインにも適用および先読みリストを使用
# add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# 証明書誤発行の防止および監視(CertificateTransparency)を1日間行い、違反した場合は接続拒否を指示する
# ただし現在はSigned Certificate Timestamp(SCT)の方が優先されており、LetsEncryptも対応済みなのでほぼ不要
# Expect-CT, SCT and Let's Encrypt
# https://community.letsencrypt.org/t/expect-ct-sct-and-lets-encrypt/142612
# add_header Expect-CT " max-age=86400, enforce, report-uri='https://アカウント名.report-uri.com/r/d/ct/reportOnly'" always;
# OCSP staplingを有効化
ssl_stapling on;
# CSCPの問い合わせ結果を検証する
ssl_stapling_verify on;
# 名前解決の際に使用するDNSサーバを指定、TTLキャッシュ時間を300秒に指定
# Google:8.8.8.8 8.8.4.4(安定性)
# Cloudflare 1.1.1.1 1.0.0.1(速度)
# Quad9 9.9.9.9 149.112.112.112(セキュリティ)
resolver 1.1.1.1 1.0.0.1 valid=300s;
# 名前解決のタイムアウト時間を指定
resolver_timeout 5s;
# OCSPの問い合わせに利用する証明書を指定(LetsEncryptで用意されている)
ssl_trusted_certificate /etc/letsencrypt/live/ドメイン名/chain.pem;
#
# Security Header 設定 (すべてにalwaysを付与する)
#
# Content Security Policy(CSP)設定
# CSPはよく調べて自サイトの構成ごとに設定する
# 以下は例
#add_header Content-Security-Policy "default-src 'none'; object-src 'none'; style-src 'self'; script-src 'self'; img-src 'self'; manifest-src 'self'; child-src 'self'; connect-src 'self'; prefetch-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; require-trusted-types-for 'script'; trusted-types default; upgrade-insecure-requests; block-all-mixed-content; report-uri https://アカウント名.report-uri.com/r/d/csp/enforce; report-to default" always;
# Reporting APIを有効化
# add_header Report-To "{'group':'default','max_age':31536000,'endpoints':[{'url':'https://アカウント名.report-uri.com/a/d/g'}],'include_subdomains':true}" always;
# Network Error Loggingを有効化
# add_header NEL "{'report_to':'default','max_age':31536000,'include_subdomains':true}" always;
# DNSの先読みを禁止し、DNSリクエスト発行による情報漏洩を防止する
add_header X-DNS-Prefetch-Control "off" always;
# クロスサイトスクリプティング(XSS)攻撃を検出した際、ページの読み込みを停止させ、レポートを送信する
add_header X-XSS-Protection "1; mode=block; report=https://アカウント名.report-uri.com/r/d/xss/enforce" always;
# ファイルのダウンロード時にファイルを直接実行させない(開かせない)
add_header X-Download-Options "noopen" always;
# iframeの制御を行い、クリックジャッキングを防止する
add_header X-Frame-Options "DENY" always;
# MIMEタイプが一致しない限りファイルを読み込みを拒否し、誤判定を利用したXSSなどの攻撃を防ぐ
add_header X-Content-Type-Options "nosniff" always;
# Adobe AcrobatおよびFlash関連のポリシーファイルを全て拒否し、クロスドメインアクセスを防止する
add_header X-Permitted-Cross-Domain-Policies "none" always;
# リファラを一切送らないことでURL情報を漏洩させない
add_header Referrer-Policy "no-referrer" always;
# アクセス時に指定した情報のキャッシュをクリアする
#add_header Clear-Site-Data "cache, cookies, storage, executionContents, *" always;
# 自サイトのコンテンツを外部で使用させない(CORP)
add_header Cross-Origin-Resource-Policy "same-origin" always;
# 埋め込むリソースに対しCORPを強制的に明示し許可していないリソースを読み込ませない(COEP)
add_header Cross-Origin-Embedder-Policy "require-corp" always;
# window.openerによるプロパティアクセスを防止する
add_header Cross-Origin-Opener-Policy "same-origin" always;
# ブラウザによる様々な特殊機能を制限する
# add_header Permissions-Policy "accelerometer=(),autoplay=(),camera=(),display-capture=(),document-domain=(),encrypted-media=(),fullscreen=(),geolocation=(),gyroscope=(),magnetometer=(),microphone=(),midi=(),payment=(),picture-in-picture=(),publickey-credentials-get=(),screen-wake-lock=(),sync-xhr=(self),usb=(),web-share=(),xr-spatial-tracking=()" always;
# 一切のキャッシュを保存および共有せず、取得するコンテンツを常に新しいものとして扱う
# MDN を信じずに「Cache-Control」には「private」を含めよう
# https://srad.jp/comment/4073928
# add_header Cache-Control "private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0" always;
# 全ファイルのキャッシュ期限を7日に指定(静的ファイルはクエリ"?="で更新判定を行うと管理しやすい(htmlはlast-modifiedで判定))
add_header Cache-Control "public, max-age=604800" always;
expires 7d;
# HTTP1.0でのアクセス時にはキャッシュしない
add_header Pragma "no-cache" always;
# Cookieの付与をhttpsでの同一サイトのみに限定し、JavaScriptでのアクセスを禁止する
# add_header Set-Cookie "__Host-id=$request_id; Path=/; Secure; Samesite=Strict; HttpOnly" always;
# ウェブサイト内でCookieを無効化、Cookieを一切必要としないサイトなら設定しても可
# Is it possible to set up nginx without cookies?
# https://stackoverflow.com/questions/45356963/is-it-possible-to-set-up-nginx-without-cookies
# proxy_hide_header Set-Cookie;
# proxy_ignore_headers Set-Cookie;
# proxy_set_header Cookie "";
上記設定内で
/etc/ssl/certs/dhparam.pem
を記述していますが、現在このファイルは存在していないため、$ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
により、Diffie-Hellman鍵交換で使われる素数を格納しているファイルを出力します(CPUの性能により数分~数十分ほど時間がかかります)。それが終わりましたら、
$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
でサイト内の個別設定を行います。
ドメイン名.conf
# IP直打ちのアクセスを排除(IP直打ちを許可するならコメントアウト)
# defalut_serverを指定することでサーバの優先順位を明示する
server {
listen 80 default_server;
listen [::]:80 default_server; # ipv6接続設定
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server; # ipv6接続設定
# 無意味なサーバネームを指定することで未定義のHostをフィルタリングする
server_name _;
# ステータスコード444はnginxがレスポンスを返さずに接続を閉じる
return 444;
}
# 上記でdefault_serverを使いたくない場合はこちら
# こちらを使う場合は本体側のlistenにdefault_serverを加えておくことを推奨
# IP直打ちでのアクセスを排除
#server {
# listen 80;
# listen [::]:80; # ipv6接続設定
# listen 443 ssl http2;
# listen [::]:443 ssl http2; # ipv6接続設定
# # サーバのIPを指定する
# server_name サーバのIPアドレス;
# # ステータス444を返す
# return 444;
#}
# httpでの接続をwww有りのhttpsにリダイレクトする
server {
listen 80;
listen [::]:80;
# server_name localhost;
# www有りを使わないなら"www.ドメイン名" は消す
server_name ドメイン名 www.ドメイン名;
return 301 https://ドメイン名$request_uri;
}
# www有りがサイトURLのデフォルトである場合
# www無しでの接続をwww有りhttpsにリダイレクトする
#server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# server_name www無しのドメイン名;
# return 301 https://www.ドメイン名$request_uri;
#}
# リダイレクトを流される側(本体)の設定
server {
# 接続するポート番号を指定
listen 443 ssl http2;
listen [::]:443 ssl http2;
# URLに表示されるドメインまたはIPアドレス
# server_name localhost;
# server_name example.com;
server_name ドメイン名;
# ウェブサイト表示時に参照されるディレクトリ
root /var/www/ドメイン名;
# indexページとして読み込むファイル
index index.php index.html index.htm;
# 初期位置
location / {
}
# 404ページの指定
#error_page 404 /404.html;
# location = /404.html {
#}
# 50*ページの指定
error_page 500 502 503 504 /50x.html;
location = /50x.html {
# 50x.htmlを参照するルートを指定
root /usr/share/nginx/html;
}
}
すべての設定が完了したら
$ sudo service nginx restart
でNginxを再起動させます。サイトにアクセスした際にhttpsから始まるURLで表示されつつ、何もエラーが発生していないならば、無事HTTPS化の完了です。
次回は静的サイトジェネレータZolaの導入および設定を紹介
執筆時点では、今回のHTTPS構成だとSSL Labsの採点で満点近いスコアを出す事が可能です。
- Qualys SSL Labs
SSL Labsの採点に関してもう一点加えるならば、DNS CAAの設定するのも良いでしょう。
- DNS CAA レコードを設定してみる
よりセキュアな構成を求める場合はCSP等の設定が求められますが、HTTPSに関しては今の所はこれで十分だと思われます。
CSPやセキュリティヘッダについての詳細は更に長くなってしまうため、別の記事で解説しようと考えています。
次回は当サイトでも採用している静的サイトジェネレータである、Zolaの導入と設定について紹介していきます。
参考資料
- Nginxでリクエスト数を制限する方法
- いまさらNginxでDoS攻撃対策してみる(3つの対応してみた)
- Ubuntu 20.04 Let’s Encryptを使用してNginxでhttps接続(Certbot使用)
- Let's EncryptでワイルドカードなSSLサーバー証明書が安価なVPSとDNSでマルチドメインWEBサーバーな話
- mozilla SSL Configuration Generator
- Setting ssl_prefer_server_ciphers directive in nginx config
- 「Let's Encrypt」のsnap版certbotのインストール手順とテスト方法
- Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定(2021年3月版)
- Let’s EncryptによるSSLサーバー証明書の取得、自動更新設定(Snapを使用しない版)
- Let’s Encrypt(無料のSSL証明書)を作成する方法
- Let’s Encryptの使い方。standaloneとwebroot
- Ubuntu環境CertBotでLet's Encryptを利用しSSL化する
- SSL証明書更新のトラブルはたまに隙を生じぬ二段構え
- Let’s EncryptのSSL証明書を更新する(手動とcronによる自動更新)
- certbot-auto更新
- ローカル開発環境の https 化
- 完全無料の独自ドメインを取得できるFreenomの使い方
- Certificates for localhost
- freenomで無料ドメインを取得する
- [SOLVED] Freenom domain not available | Freenom account registration | Freenom Technical error
- localhost(127.0.0.1)をHTTPS化して開発環境を作る
- Local 環境の https 化
- Private IP アドレスの Nginx を Let's Encrypt の証明書で HTTPS 運用
- Let’s EncryptでワイルドカードなSSL証明書を入れる ? Amazon Linux2編
- Let's Encrypt と Route 53 でローカル開発環境を HTTPS 化する
- ローカルホスト(127.0.0.1)用のSSL証明書を取得する
- Let's encrypt運用のベストプラクティス
- Ubuntu 20.04 certbot-dns-cloudflareでワイルドカード証明書を取得、更新
- Freenom網域搭配Certbot自動取得與更新Wildcard SSL憑證
- SSL Server Test
- CryptCheck
- nginxでのSSL/TLS設定の備忘録
- nginxを自己満足でセキュリティ強化する(TLSv1.3)
- nginxとLet's EncryptでQualys SSL Labsの評価をA+にしたい
- Strong SSL Security on nginx
- インストール直後のnginxからSSL評価A+とセキュリティ強化を行う手順メモ
- SSL SERVER TESTで A+の評価をとる
- Qualys SSL Labs の SSL Server Test で全項目100点をとるためにした事
- Let's EncryptのSSL証明書で、Qualys SSLTestでA+評価を獲得するには
- SSL証明書 "Let's Encrypt"の自動更新を設定してみた!
- Ubuntu 20.04 Let’s Encryptを使用してNginxでhttps接続(Certbot使用)
- Setting ssl_prefer_server_ciphers directive in nginx config
- Mozilla Observatory
- Security Headers
- CSP Evaluator
- OWASP Secure Headers Project
- Web Security
- HTTP ヘッダー
- Frequently Asked Questions
- How to configure Security Headers in Nginx and Apache
- 今夜つける HTTPレスポンスヘッダー (セキュリティ編)
- Webサーバをセキュアに保つ設定のまとめ(Apache)
- Observatory by Mozilla でA+評価を取得した設定例(2017-01-15時点)
- Nginx設定の肝
- Content Security Policyを設定してウェブサイトをXSSから守る
- How to Implement Security HTTP Headers to Prevent Vulnerabilities?
- Cross-Origin-Resource-Policyヘッダとは
- Content Security Policy Level 3におけるXSS対策
- Content Security Policy でユーザーを守ろう
- Cookie 概説
- Subresource Integrity (SRI)
- CDNのリソースを使う時はSRIを利用したほうが良い
- Nginxでキャッシュをする際の、Cookieに関する注意点
- Nginxでのアクセス流量制御を検証してみた