2022.07.08

Nginx の HTTP/3 対応を完全に理解する

D. M. です。
Nginx の HTTP/3 対応はどんなことが必要なのかを完全に理解しようとしてローカルに構築した際ハマった問題点をご紹介します。

TL;DR


・nginx.confは以下3行が新たに必要
listen 443 http3 reuseport;
ssl_protocols TLSv1.3;
add_header Alt-Svc 'h3=":443"; ma=86400';

・SSL証明書はホンモノ必須!(LetsEncryptでもOK)

・ALT-SVC ヘッダーがないとブラウザは判断できない!そこに h3 を書け!

・WSL 2だと動かないぞ!VirtualBox 復活祭だ!

・ブラウザキャッシュにハマると全くHTTP/3にならない!サーバの設定を変更したらブラウザキャッシュをクリアせよ!


モチベーション


背景として、2022年6月6日 IETF(Internet Engineering Task Force)により HTTP/3 が RFC 9114 として勧告されました。

歴史的にはHTTPはこんな感じで定期的に進化しています。
1997  HTTP/1.1
2015  HTTP/2 (SSL必須化)
2022.6 HTTP/3
HTTP/3は先日標準化されこれから普及というところですが、Googleでは結構前からYoutubeなどを中心に使われています。

私は開発プロジェクトでは Nginx を利用しており、なるべく早く HTTP/3 に対応させたいと考えています。

Nginx は公式に2020年6月10日からプレビュー版のソースを公開し、自分でビルドすれば誰でも HTTP/3 対応の Nginx を使うことができます。
https://www.nginx.com/blog/introducing-technology-preview-nginx-support-for-quic-http-3/
2022年7月現在はまだ開発中ですが、もうすぐにでもプロダクションレベルになるものと思われます。(希望)
現段階でほぼ固まってきているであろう仕様を理解することで、スムーズに Nginx の HTTP/3 対応を行いたいというのがモチベーションです。

HTTP/3とは


メリット
・接続とデータ転送がとにかく早い。
・0 RTT(ゼロラウンドトリップ)で通信の確立と開始が早い。(何度もやり取りしない)
・QUICなのでデータ転送が早い。(再送に無駄がない)

主要なところを書くとこんな感じです。
もっと詳しく知りたい人はググってみると豊富な記事が出てきます。
まさにいまが旬の技術で、おそらくこれから主流になるので詳しく理解しておきたいところです。

個人的には以下のサイトが好きです。

HTTP/3 の特徴 HTTP/2とQUICの違い
https://blog.redbox.ne.jp/http3-quic.html

HTTP/3 explained
https://http3-explained.haxx.se/ja

The Illustrated QUIC Connection
https://quic.xargs.org/

パフォーマンス向上のデータ
Google 公式が HTTP/3 により速度向上のデータを紹介しています。

We’ve found that IETF QUIC significantly outperforms HTTP over TLS 1.3 over TCP. In particular, Google search latency decreases by over 2%. YouTube rebuffer time decreased by over 9%, while client throughput increased by over 3% on desktop and over 7% on mobile.

 
IETF QUICは、HTTP over TLS 1.3overTCPよりも大幅に優れていることがわかりました。特に、Googleの検索レイテンシは2%以上減少します。 YouTubeの再バッファリング時間は9%以上減少しましたが、クライアントのスループットはデスクトップで3%以上、モバイルで7%以上増加しました。

Chromium Blog: Chrome is deploying HTTP/3 and IETF QUIC
https://blog.chromium.org/2020/10/chrome-is-deploying-http3-and-ietf-quic.html

導入するといいことがありそうな気がしてきました!

このブログの目的


今回の目的は以下となっています。
・既存のNginxの設定から何を変更すればいいのかHTTP/3にできるのかを完全に理解する。
・Windows のローカルで構築した場合にぶち当たった細かい問題点を紹介する。

そもそもビルドする方法


とりあえず動く環境を作ってみたいと思います。

公式が2021年7月に実験的なビルドを行うための Dockerfile を公開しています。
https://www.nginx.com/blog/our-roadmap-quic-http-3-support-nginx/

自分で 1 からやろうかと思いましたが、少し検索するとこれをアレンジした Github のレポジトリが大量に見つかるので今回はそれを拝借します。
https://github.com/macbre/docker-nginx-http3
あまり深く考えずにスピードを重視してとりあえず目についたこちらを採用して構築してみました。ありがとうございます。(実はこれが後述するハマる原因に。。)

ビルド時のポイントですが、TLSv1.3 を利用するために BoringSSL と依存関係を持っています。( OpenSSL の quictls フォーク版でも可能)。また http_v3_module, stream_quic_module を読み込ませます。Nginx のコンパイル時にこれらを指定する必要があります。

$ ./auto/configure –with-cc-opt=”-I../boringssl/include” \
–with-ld-opt=”-L../boringssl/build/ssl \
-L../boringssl/build/crypto” \
–with-debug –with-http_v3_module \
–with-stream_quic_module

さらに、有志の方がテスト用に Curl を HTTP/3 対応させたものがあり、 Dockerfile および Docker Image が公開されているので、合わせて利用すると検証が楽かと思います。多謝!
https://qiita.com/keys/items/611d949ca26d6ca848c2

ローカルに環境構築


以下ローカル環境のバージョン情報です。
Windows 10
VirtualBox 6.1.32 上の Ubuntu 20.04.4
Docker 20.10.17
Nginx 1.21.6

自分が運営に関わっているサイトの証明書を入れて無理やりローカルで動かしました。




いざブラウザでアクセス!
Chrome のツールで見えると Protocol が h3 になっています!

本題:Nginx の設定ファイルは HTTP/3 にするとどこが変わるのか?


HTTP/3 版の Nginx に絶対必要な記述を順に解説していきます。(元ネタはこちらの公式URL。これをもとにちょっと変更しています)

server {
    listen 443 ssl;              # TCP HTTP/1.1
    listen 443 http2;            # TCP HTTP/2
    listen 443 http3 reuseport;  # UDP QUIC+HTTP/3

    ssl_protocols       TLSv1.3; # QUIC requires TLS 1.3
    ssl_certificate     ssl/mydomain.crt;
    ssl_certificate_key ssl/mydomain.key;

    add_header Alt-Svc 'h3=":443"; ma=86400';   # HTTP/3 が使えますよとブラウザに伝える
    add_header QUIC-Status $http3;     # QUIC で通信してますよという変数。なくても動く。
}

まず1行目。
listen 443 http3 reuseport;
HTTP/3 は UDP を使いますが、ポートは同じ443を指定するのがセオリーのようで、 reuseport を指定すると 1 つのポートを複数のプロセスで bind できます。ブラウザ的には自然に同じ処理を継続してくれます。
実は既存の listen 443 http2; も必須です。
これはブラウザからの最初の通信は現状 HTTP/2 で試みられるためです。何らかの問題(中継のプロキシサーバが UDP が通信できないなど)があると HTTP/3 は使われず HTTP/2 を継続して使うことになります。

ドハマリポイント SSL証明書

HTTP/3 の場合、 SSL 証明書はホンモノである必要があります。
非常に当たり前なのですが、開発環境やローカルではここがとてもめんどい。
Chrome をはじめブラウザの証明書チェックは厳しくなっており、 localhost でオレオレ証明書を作ったりしても正常にページを開くことができません。HTTP/3通信もできませんでした。
ここがわかっていないとどうして HTTP/3 通信にならないのかわからないので非常にハマるポイントです。

この証明書問題は以下がとても詳しいです。
https://scrapbox.io/nwtgck/Chrome%E3%81%A7HTTP%2F3%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%82%92localhost%E3%81%AB%E7%AB%8B%E3%81%A6%E3%81%A6%E8%87%AA%E5%B7%B1%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%82%92%E4%BF%A1%E9%A0%BC%E3%81%95%E3%81%9B%E3%81%A6%E3%82%82HTTP%2F3%E9%80%9A%E4%BF%A1%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84_+_%E8%A7%A3%E6%B1%BA%E7%AD%96

結論としては
・独自に無料のドメインをテスト用にとって Let’s Encript を発行する。(上述の URL の方法)
・運営しているサイトの証明書をNginxに入れる。該当ドメインはHostsファイルをいじって自分のローカルのIPでアクセスできるようにする。

正直ここが一番面倒かもしれません。ローカルの開発環境は無理に HTTP/3 で構築しないというのが普通になりそうです。

ドハマリポイント Alt-Svc


Nginx は HTTP/3に対応している場合、レスポンスで Alt-Svc (Alternate service) ヘッダーを返します。
これはブラウザに対して「いまhttp2で通信してるけど、ちなみに私は同じサービスを別の場所でも実行していますよ」と教える役割を担っています。

add_header Alt-Svc ‘h3=”:443″; ma=86400’;

ALPN プロトコルとして h3 がポート 443 で通信できますよ。max-age は 86400秒ですよ。という意味です。
Web上にある例ではこの h3 には h3-29 のようにバージョンが記載されていることがあります。私は最初にビルドした Dockerfile に h3-29 という記載があったのですが、実はこれでハマりました。 Curl では問題ないのに、ブラウザとの通信がなぜか一向に HTTP/3 になりませんでした。

結論としては HTTP/3 版 Nginx ではもはや h3 以外をサポートしておらずそもそも通信ができないようです。
README には IETF QUIC Version 1 をサポートし、ドラフト版をサポートしないとあります。
https://quic.nginx.org/readme.html
そのためドラフト版を指定していると HTTP/3 が開始されずずっと HTTP/2 で通信してしまいます。
この問題は正しく h3 を指定することで解決できました。

alt-svc を理解できるブラウザは以下にあるので念のため一読をお勧めします。
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Alt-Svc

ドハマリポイント WSL


Docker Desktop はもう有料となってしまったので、私は Windows での Docker 利用にだいぶ消極的になっています。
したがって開発環境として WSL 2 で Ubuntu を動かしており、今回はそこに Docker を入れて Nginx をビルドして動作させました。
雰囲気以下のような感じで TCP 443 と UDP 443 を開けています。

docker run \
-p 0.0.0.0:80:80 \
-p 0.0.0.0:443:443/tcp \
-p 0.0.0.0:443:443/udp \
-t nginx-quic

そして Ubuntu上で HTTP/3対応させた Curl を利用するとしっかりHTTP/3としてレスポンスがありました。

しかし、 Windows 上のブラウザからアクセスするとどうしてもHTTP2になってしまいます。

これは原因は WSL 2 が UDP の通信ができないことのようでした。
そうなると Docker 使えないやんけと絶望したのですが、ここでなんと VirtuakBox が使えます!Virtualbox は ポートフォワーディングの機能で UDP を指定可能で、やってみたところ全く問題なく動作しました。奇跡の救世主 Virtualbox に感謝です。



ドハマリポイント ブラウザキャッシュ


最後の敵はブラウザキャッシュです。
HTTP/3とHTTP/2を切り替えて動作確認をしていると、Curlでの確認ではNginxの設定はHTTP/3になっているのにブラウザからはHTTP2のまま戻らないという事象に出くわしました。
ここは本当に原因がわからず長時間ハマってしまいました。
結論としてはブラウザは同じサイトへの接続プロトコルをキャッシュしていることが原因でした。
add_header Alt-Svc ‘h3=”:443″; ma=86400’;
こちらに関連していると思うのですが、 ma は max-age すなわち有効期限なので、ブラウザ内のキャッシュに一時的な状態を保存しています。
alt-svc があっても HTTP/3 が1度使えなくなると、ブラウザはそのサイトへの接続方法として HTTP/2 の状態を記憶してしまうという動作をします。
OS再起動しても残っていたりしたのですが、明示的にキャッシュをクリアすることで解決することができました。



add_header QUIC-Status $http3;

$http3 は HTTP において HTTP\3 が利用されているか判定する変数です。また $quic は Stream が QUIC ですよと変数のようです。これによりレスポンスヘッダーに quic-status: h3 が明示されます。
https://quic.nginx.org/readme.html

他の記述

以下は無くても動作するのでオプションです。

ssl_early_data on;
こちら実はHTTP/3固有の機能ではなく、TLSv1.3の方の機能ですが、HTTP/3対応すると使えるタグなので一言だけ紹介しておきます。
TLS1.3の0-RTTハンドシェイクを有効にしてくれるので、1度通信済みで鍵共有できている相手とはハンドシェイクと一緒にデータが送信されます。早くなるはずです。
http://mogile.web.fc2.com/nginx/http/ngx_http_ssl_module.html#ssl_early_data

また、設定ファイルで TLSv1.3 を指定していないと以下のようなエラーが出ます。(ここはすぐにわかるのでハマっていないのですが、念のためメモ)

nginx.conf の以下の記述
ssl_protcools TLSv1.2; # ← TLSv1.3 を記載していない

エラーログ
nginx: [emerg] “ssl_protocols” must enable TLSv1.3 for the “listen … http3” directive in /etc/nginx/conf.d/https.conf:2


パフォーマンステスト


自分のローカルで速度を確認してみました。
ローカルの VirtualBox の Ubuntu に構築した Nginx-QUIC に対して Windows の Chrome ブラウザからリクエストを飛ばしてみます。
Nginx が公式に用意している以下のサイトを参考にしてローカルに構築しました。
https://quic.nginx.org/quic.html

このサイトはバニラの Javascript で Service Worker を用いていてひたすらリクエストを発行します。
テスト用に、こちらを自分のVirtualBox上のサイトへアクセスするように作り変えました。
https://point.gmo.jp/?id=186 のように連番IDを付与する形式で、リクエストヘッダーには Cache-Control: no-cache が付いており、都度処理をさせるため 304 Not Modified は発生しません。
HTTP/3と同じNginxでhttp2で起動した場合で比較しています。

#単にここをコメントアウトすればHTTP2に戻せる
#listen 443 http3 reuseport;

同時100接続で合計10000リクエストを何秒でさばけるかを計測します。
3回実行して平均を出します。


結果です。

HTTP/3 75秒
HTTP/2 51秒

完敗。。

なぜ。。

同時200などの負荷を上げる方法で試してみましたが、リクエスト失敗が増えるなど悪化しただけでした。
HTTP/3 の場合ブラウザは高負荷でサーバ接続まで4秒程度待たされると Request Fail となって失敗。
Nginx 接続までいければレスポンス速度は出ますが、そもそもが全体が遅いので処理開始に至らないようです。


緑はQUICで正常に動いた箇所です。黒いところは高負荷になって使えなくなりブラウザのほうがあきらめてTCPのHTTP2の通信に切り替えています。(結果安定して最後まで実行)
Nginx がまだ開発中のバージョンなのでそれが問題なのか、もしかしたらUDPの通信上の問題かもしれません。
明確なボトルネック特定までできず課題は積み残しとなりました。

宣伝

グループ研究開発本部 次世代システム研究室では、最新のテクノロジーを調査・検証しながらインターネット上の高度なアプリケーション開発を行うエンジニア・アーキテクトを募集しています。募集職種一覧 からご応募をお待ちしています。

Pocket

関連記事