こんにちは。
Chromium系ブラウザではStable版でもQUIC
及びHTTP/3
通信が可能になりました。
新しい物好きとしては是非とも導入したいということでNGINXのQUICプレビュー版を用いてQUIC
及びHTTP/3
対応サーバーを構築します。
幸いにも公式のREADMEが丁寧なので、それをなぞれば簡単に導入できます。
なお、先達が同様の記事を書かれていらっしゃいますが、今回は下記の通りNGINXが公式に発表したブランチを用いて構築します。
このQUIC + HTTP/3の実装は完全に新しいものであり、quicheプロジェクトの一部としてCloudflareが提供するパッチとは関係ありません。
ソースからビルドしますが、目指すのはapt install
したNginx
と同じ使用感です。
自分がハイハイをしていた頃、(上級者の方々には当たり前のことも)細かく書いている記事が有難いと感じたことが多々あったので、細かくコメントを書いていきたいと思います。
追記
記事を書いてからしばらくたち、HTTP/3の仕様もだいぶ定まってきました。
それに伴ってnginx-quic
にも変更があったので記事内容を更新しました。
- プロトコルの変更
-
h3-29
=>h3
(2021/08/03) -
We support IETF QUIC version 1. Internet drafts are no longer supported.
(2022/02/12) -
configure.sh
内容変更 -
--with-http_quic_module
の削除(2022/02/12) - 設定ファイルの変更
-
$http3
変数の削除(2021/08/03)
環境
AWS Lightsail
上のDebian
で行っていきます。
最近初めて使いましたが、某cherry blossomsよりお手軽で便利だと感じました。
Amazon Linux 2
でも行いましたので追記しました。
Linux ip-***-***-***-*** 4.19.0-14-cloud-amd64 #1 SMP Debian 4.19.171-2 (2021-01-30) x86_64 GNU/Linux
シェバングは適宜書き換えてください。
下準備
利用するプロトコルが違うので、きちんと設定すればQUIC版
とStable版
のデュアルスタック(?)的なことも可能ですが、その場合はconfigure
をごにょごにょする必要があります。
なんてことはない作業ですので解説はしませんが、解説がないとできない人は素直にsudo apt purge nginx nginx-full && sudo apt autoremove --purge
でStable版
をアンインストールしてからインストールするのがよいでしょう。
特に今回の設定ではapt
経由と同じディレクトリにインストールすることを目指すので削除してから臨むのがよいと思います。
apt purge
すると従前の設定ファイルが消えるので保存しておきたい方はバックアップ(sudo mv /etc/nginx /etc/nginx.bak
)をおすすめします。
必要なパッケージをインストールします。
Ninja
を使用するとBoringssl
のビルドを高速化できるらしいです。
# Debian系
sudo apt update && sudo apt upgrade
sudo apt install git mercurial cmake make golang libunwind-dev libpcre3-dev zlib1g-dev gcc g++
# Debian系(Ninjaを使用)
#sudo apt install git mercurial cmake ninja-build golang libunwind-dev libpcre3-dev zlib1g-dev gcc g++
# RedHat系(Ninjaを使用)
#sudo yum update
#sudo yum install git hg cmake3 ninja-build golang libunwind-devel pcre-devel zlib-devel gcc gcc-c++
Boringssl
まずはGoogle
さんが作ったOpenSSL
のフォークをインストールします。
適当なディレクトリ(今回は/opt/nginx
とします)を用意して、BoringSSL
をダウンロードします。
ダウンロードしたディレクトリにあるBUILDING.MDを参考にビルドします。
Ninja
を用いたインストールが速いらしく推奨されていますが、今回はMake
でいきます。
# ディレクトリを作る
sudo mkdir /opt/nginx
# 移動
cd /opt/nginx
# git clone
sudo git clone https://boringssl.googlesource.com/boringssl
# インストール
cd boringssl
sudo mkdir build
cd build
# [1]or[2]へ
# [1] makeでインストール
sudo cmake .. # 何か不足していると言われればインストールする
sudo make # ちょっと時間がかかります
# テスト
cd ..
go run util/all_tests.go # All tests passed!となればOK(時間がかかります)
cd ssl/test/runner
go test # "PASS ok"と出ればOK(時間がかかります&メモリを食う)
# [2] Ninjaでインストール
#sudo cmake -GNinja .. # Redhatでは"sudo cmake3 -GNinja .."
#sudo ninja # Redhatでは"sudo ninja-build"
# テスト - Ninjaを使用した場合は二つのテストが一つのコマンドで完結します
#cd ..
#sudo ninja -C build run_tests # Redhatでは"sudo ninja -C build run_tests"
本当はもう一種類テストがあるのですがどうやってもエラーなのと、放置しても進めるので今回はパスします。
下記備考の通り、インスタンスのメモリ不足が原因でした。
備考
インスタンスのスペックによってはコンパイル時に以下のようなエラーになることがあります。
Debian
では問題なかったのですが、Amazon linux 2
でハマりました。
g++: internal compiler error: Killed (program cc1plus)
Please submit a full bug report, with preprocessed source if appropriate.
See <http://bugzilla.redhat.com/bugzilla> for instructions.
このエラーはメモリ不足が原因です、依存関係ではありません…。
や、まぁ確かにKilled
とは書かれていますが…。
初めてだったので依存関係と思い込んで解決まで苦労しました。(本当に苦労しました)
メモリ不足で失敗しているので、不要なプロセスを終了してから再挑戦するか、インスタンスのスペックを上げることを検討しましょう。
私は1GBに上げて解決しました。
また、コンパイル後のテストも二つとも成功しました。
Nginx
では、いよいよエェンジンエッックス
をインストールしていきます。(名前かっこいいですよね)
cd /opt/nginx
# git clone的な
sudo hg clone -b quic https://hg.nginx.org/nginx-quic
次にオプションを設定していきますが、今回は**apt
を用いてインストールした時と同じようになるよう**にインストールしたいと思います。
要するに/etc/nginx
配下に設定ファイル諸々を、そして/usr/sbin/
配下に実行ファイル、/var/log/
配下にログファイル、etc...ということです。
ダイナミックモジュールは使いません。
sudo nano /opt/nginx/configure.sh
で下記のファイルを作成します。
#!/usr/bin/bash
cd nginx-quic
./auto/configure \
--with-debug \
--with-http_v2_module \
--with-http_v3_module \
--with-stream \
--with-stream_quic_module \
--with-http_ssl_module \
--with-cc-opt="-I../boringssl/include" \
--with-ld-opt="-L../boringssl/build/ssl \
-L../boringssl/build/crypto" \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/run/nginx.pid \
--lock-path=/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=www-data \
--group=www-data
今回の設定で基本的なサーバー動作は可能ですが、モジュールは必要に応じて足し引きしてください。
追記(8/3)
PHPが503となる場合は--with-stream
が指定されていない可能性があります。
当初、不要と思い省いていましたが、UNIXソケットへPHPを渡す際に必要になるようです。
ファイルを作成出来たらインストールを続行します。
# 実行権付与
sudo chmod 744 configure.sh
# configure.sh実行
sudo -s ./configure.sh # エラーなく進んでConfiguration summaryが出ればOK
cd nginx-quic
# インストール
sudo make # ./objs配下にファイルができます
sudo make install # ファイルを各所にコピーします(既に別バージョンがインストールされている場合は注意)
# 確認
sudo -s /usr/sbin/nginx -V # インストールした内容できちんと表示されればOK
以上でインストール自体は完了です。
簡易設定
とりあえず動作確認だけできるように設定します。
nginx.conf.default
で同じものが用意されていますので、インストール時に用意される設定ファイルはいったん削除して新しく作り直します。
sudo rm -f /etc/nginx.conf
sudo nano /etc/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
server_tokens off;
charset utf-8;
include mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
server {
listen 80;
listen [::]:80;
index index.html;
root /var/www/html;
}
}
IPv6
推進の過激派なので最小設定といえどIPv6
待ち受けは必須。
最後に諸々のディレクトリなどを用意します。
sudo mkdir /var/cache/nginx
sudo -u www-data mkdir /var/www # エラーが出たら下記「エラーがでたら」を参考にして解決して下さい
sudo mv /etc/nginx/html /var/www/
systemd管理
言わずもがな、手動で起動するのは非現実的ですので設定していきます。
まずはユニットファイルを作成します。
これは公式ページにあるものを丸ぱくりです。
sudo nano /lib/systemd/system/nginx.service
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target
[Service]
Type=forking
#PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
ラズパイやさくらでは問題なかったのですが、PIDFile
でなぜか怒られました。
fork
したPID
はsystemd
が自動で追跡できるので明示しなくても問題ないと判断しコメントアウトします。
それでは、ユニットファイルを読み込んで起動させていきます。
sudo systemctl daemon-reload
sudo systemctl start nginx
エラーが出たら必要なファイルを用意するなど適宜対応してください。
問題なく起動できればブラウザででサーバーのIPアドレスを打ち込んでアクセスしてみましょう。
**Welcome to nginx!
**というページが表示されれば成功です。
問題がなければ自動起動するようにします。
sudo systemctl enable nginx
エラーがでたら
下記のようなエラーに遭遇することがあります。
nginx[xxxx]: nginx: [emerg] getpwnam("www-data") failed in ...
これは、www-data
というユーザーが存在しないと怒られているので以下のようにして追加します。
www-data
というユーザーを追加して、ホームディレクトリを/var/www
にして、ログインを禁止しています。
passwd
でパスワードを設定してはいけません。
sudo useradd -d /var/www -s /usr/sbin/nologin www-data
SSL証明書取得
これはちょっと違う話になるので別記事(certbotの導入手順が新しくなっていた件)で扱います。
既に発行済みの方はそちらを利用してください。
本番設定
皆様お好みの設定があると思うので、HTTP/3及びQUICを含めて取り敢えず動く為に必要な最低限の設定だけ書いておきます。
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
####
# Basic settings
####
server_tokens off; # Hide server information
charset utf-8;
include mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
####
# HTTP/3 and QUIC minimal settings
####
ssl_protocols TLSv1.3; # TLSv1.3 is required, but you can add TLSv1.2 for compatibility
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; # Uncomment to disable unsafe ciphers if you use TLSv1.2
# quic_retry on; # Uncomment to enable address validation
# ssl_early_data on; # Uncomment to enable 0-RTT
server {
listen 443 ssl http2; # Listen to legacy connections (HTTP/2)
listen [::]:443 ssl http2; # Same as above, for IPv6
listen 443 http3 reuseport; # Listen to cutting-edge connections!! (HTTP/3,QUIC)
listen [::]:443 http3 reuseport; # Same as above, for IPv6
server_name example.com;
index index.html index.php;
root /var/www/example.com;
ssl_certificate path2certs/example.com.crt;
ssl_certificate_key path2certs/example.com.key;
location / {
try_files $uri $uri/ =404;
#add_header Alt-Svc '$http3=":443"; ma=86400'; # Older version
add_header Alt-Svc 'h3=":443"; h3-29=":443"; ma=86400'; # Tell browsers that HTTP/3 is available and its listening port
add_header QUIC-Status $quic; # Indicate using quic
}
}
####
# Redirect HTTP to HTTPS
####
server {
listen 80;
listen [::]:80;
return 301 https://$host$request_uri;
}
}
Nginx
を触ったことがある人なら分かると思いますが、一応所々にコメントを付しておきましたので参考にしてください。
変数(追記参照)$http3
は使用しているプロトコルが返りますので、現在であればh3-29
*(IETF QUIC)*となります。
もう一つ$quic
という変数も用意されており、こちらはQUIC
が利用されている際に文字列quic
が返ります。
追記(8/3)
変数$http3
は削除された模様です。
私見ですが、HTTP/3
がIETFにより正式に策定されたため記述方法を変数化する必要がなくなったのかと思われます。
Googleの書き方を参考に最低限の記述量で済むように書き換えました。
最終的にはh3=":443"
だけで済むようになるのではないでしょうか。
ただ、現在ではh3-29
を含めないと読み込まれない事象も多々見受けられます。
要点は以下の二つです。
-
TLSv1.3
を有効にする -
listen ... http3 reuseport
で待ち受ける -
add_header Alt-Svc '$http3=":443"'
で伝える
サーバーはネコでブラウザはタチだけどお互い知らないので交渉前にサーバー側からネコって自己申告するという話です。
失礼。
確認
実際にどのプロトコルで通信しているかは、デベロッパーツールのネットワークタブで表のタイトル行を右クリックしてProtocol
にチェックを入れれば確認できます。
http/1.1
はそのままHTTP/1.1
で、h2
はHTTP/2
、~~h3-29
はHTTP/3
~~です。
追記(8/3)
HTTP/3
はh3
となっています。
ファーストコンタクトには適用されず(ここでAlt-svc
を受け取る)、二つ目以降のコネクションから~~h3-29
~~h3
が適用されます。
Cache-Control: no-cache
のヘッダーが適用された場合などでキャッシュを読み込むとh3-29
とはならないので、デベロッパーツールのDisable cache
にチェックを入れてリロードしてみてください。
キャッシュの削除とハード再読み込み
をするとAlt-svc
の情報も消えてしまうので注意してください。
add_header QUIC-Status $quic;
を設定した場合はレスポンスヘッダーからQUIC
が使用されているかを確認できます。
QUIC-Status: quic
が有れば有効、なければ無効です。
アップデート
試験版なので数日ごとに更新がありますから、手軽にアップデートをできるようにします。
sudo nano /opt/nginx/update.sh
で以下の内容のファイルを作成します。
Debian系
でNinja
を使用しない前提で書いてあるので環境に合わせて書き直してください。
#!/usr/bin/bash
git -C /opt/nginx/boringssl pull | grep -q 'Already up to date'
boringStatus=("${PIPESTATUS[@]}")
if [ ${boringStatus[0]} -eq 0 ]; then
if [ ${boringStatus[1]} -gt 0 ]; then
cd /opt/nginx/boringssl/build
cmake ..
cmakeStatus=$?
make
if [ $? -eq 0 -a $cmakeStatus -eq 0 ]; then
echo 'BoringSSL update(s) installation has succeeded.'
boringUpdated=0
else
echo 'BoringSSL update(s) installation has failed.(Installation failure)'
boringUpdated=1
fi
else
echo 'BoringSSL is already up to date.'
boringUpdated=1
fi
else
echo 'BoringSSL update(s) installation has failed.(Sync failure)'
boringUpdated=1
fi
hg --cwd /opt/nginx/nginx-quic pull -u | grep -q 'no changes found'
nginxStatus=("${PIPESTATUS[@]}")
if [ ${nginxStatus[0]} -eq 0 ]; then
if [ ${nginxStatus[1]} -gt 0 -o $boringUpdated -eq 0 ]; then
/opt/nginx/configure.sh
cd /opt/nginx/nginx-quic
make
if [ $? -eq 0 ]; then
systemctl stop nginx
make install
if [ $? -eq 0 ]; then
systemctl start nginx
echo 'NGINX update(s) installation has succeeded.'
exit 0
else
echo 'NGINX update(s) installation has failed.(Installation failure)'
exit 1
fi
else
echo 'NGINX update(s) installation has failed.(Building failure)'
exit 1
fi
else
echo 'NGINX is already up to date.'
exit 0
fi
else
echo 'NGINX update(s) installation has failed.(Sync failure)'
exit 1
fi
exit 1
sudo chmod a+x /opt/nginx/update.sh
で実行権を付与して、あとは手動で実行するなり、cronで定期実行するなりすればOKです。
grep
で拾うこの方法はスマートじゃなくて嫌なのですが、もう少しスマートな方法があれば教えてください。
最後に
結構簡単にHTTP/3
とQUIC
に対応したサーバーが構築できます。
あまり体感するほどではないですが、気持ちはよいです。
QUICプレビュー版
のバージョンは1.19なのでapt
からインストールできるようになるのは少し先の話かもしれません。
基本的には安定しているので、非商業用であれば一足先に導入してみるのもよいですね。
当方ではPHP
で時折400 Bad request
が発生する問題がありますが、解決方法は未だ見つけられていません。
ご存じの方がいましたら教えてください。
追記(8/3)
アップデートで概ね解決しています。
未解決の方は、configure時に--with-stream
と--with-stream_quic_module
オプションが指定されているかを確認してください。
また、OCSP stapling
には非対応です。
最後までお読みいただきありがとうございました。