nginxでHTTP/2・HTTP/3(QUIC)を有効にする

※本ページには広告(アフィリエイトプログラム等)が含まれます。

nginxで動かしているサイトのプロトコルを久々に確認したら、まだHTTP/1.1のままだったということはないだろうか。HTTP/2はもう珍しくないが、HTTP/3(QUIC)まで対応しているサーバーはまだ少数派だ。ここではさくらのVPS(Debian 12)を前提に、HTTP/2とHTTP/3の両方を有効化する手順をまとめる。

HTTP/2・HTTP/3のメリット

HTTP/1.1は1接続につき1リクエストずつ順番にしか処理できず(厳密にはパイプライニングはあるが実質使われていない)、ブラウザは複数のTCP接続を張って並列化でごまかしていた。HTTP/2は1つのTCP接続の上で複数のリクエスト・レスポンスを多重化(multiplexing)できるため、コネクション数を増やさずに並列取得ができる。加えてHPACKによるヘッダ圧縮で、Cookieなど繰り返し送られるヘッダのオーバーヘッドが減る。

HTTP/3はさらに一歩進んで、輸送層をTCPからUDP上のQUICに変える。QUICはTLS 1.3のハンドシェイクを輸送層のハンドシェイクと統合しているため、初回接続が速い(0-RTT/1-RTTで済む)。さらにTCPの「先頭パケットが詰まると全ストリームが止まる」Head-of-Line Blockingが起きないので、モバイル回線などパケットロスが多い環境で効果が大きい。実際に体感できるのはモバイルユーザーが多いサイトだと思う。

HTTP/2を有効にする

nginx 1.25.1以降ではhttp2ディレクティブがlistenから独立した。旧来は次のようにlistenのオプションとして書いていた。

# 旧記法(nginx 1.25.1未満、または後方互換のため今も動く)
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name [あなたのドメイン];
    ...
}

新しいnginxでは非推奨警告が出るので、次の書き方に直す。

# 新記法(nginx 1.25.1以降推奨)
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name [あなたのドメイン];

    ssl_certificate     /etc/letsencrypt/live/[あなたのドメイン]/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/[あなたのドメイン]/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ...
}

HTTP/2はブラウザ実装上TLS必須(平文のh2cはブラウザがサポートしない)なので、ssl_protocols TLSv1.2 TLSv1.3;は必ず設定しておく。TLSv1.0/1.1は互換性メリットがほぼない上に脆弱性の温床になるので今どき有効化する理由はない。

HTTP/3(QUIC)を有効にする

HTTP/3はUDPで待ち受けるため、listen行をもう1組追加する形になる。TCPのHTTP/2用listenは残したまま、QUIC用listenを並べるのが基本パターンだ。

server {
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name [あなたのドメイン];

    ssl_certificate     /etc/letsencrypt/live/[あなたのドメイン]/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/[あなたのドメイン]/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_early_data on;

    # HTTP/3で待ち受けていることをブラウザに広告する
    add_header Alt-Svc 'h3=":443"; ma=86400' always;
    ...
}

reuseportはワーカープロセスごとにソケットを分散させるオプションで、QUICのように大量の小さいUDPパケットを捌く場合は必須級。Alt-SvcヘッダはまずHTTP/2経由でアクセスしたブラウザに「このホストはUDP 443番でHTTP/3も喋れるよ」と教えるためのもので、これが無いとブラウザは永久にHTTP/3を試さない。ssl_early_dataは0-RTT再開を許可する設定だが、リプレイ攻撃の余地があるので冪等でないPOSTを受けるエンドポイントでは注意が必要になる。

nginxのビルドとファイアウォールの落とし穴

HTTP/3を使うには、nginxが--with-http_v3_module付きでビルドされている必要がある。確認は次のコマンドで。

nginx -V 2>&1 | grep -o with-http_v3_module

Debian 12(bookworm)の公式リポジトリのnginxは執筆時点では比較的古く、QUICモジュールが入っていないことが多い。その場合はnginx公式(nginx.org)が提供するリポジトリに切り替える必要がある。

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
  | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/mainline/debian bookworm nginx" \
  > /etc/apt/sources.list.d/nginx.list

apt update && apt install -y nginx

mainlineリポジトリを使うと最新機能は手に入るが、Debian公式パッケージほど枯れていない点は割り切る。もう一つ忘れがちなのがファイアウォールだ。HTTP/3はUDPなので、TCPの443番だけ開けていても届かない。

ufw allow 443/tcp
ufw allow 443/udp

ここでUDPを開け忘れて「設定は合っているはずなのにh3にならない」とハマるケースが多い。さくらのVPSはデフォルトでufw未導入のことが多いが、外部ファイアウォール(パケットフィルタ)を使っている場合はそちら側の設定も確認する。

動作確認

設定を反映したらnginx -tで構文チェックしてからsystemctl reload nginx。確認はcurlのHTTP/3対応ビルドが手元にあれば一番早い。

curl --http3 -I https://[あなたのドメイン]

HTTP/3 200が返れば成功。curlが対応していない場合は、ブラウザ(Chrome/Firefox)の開発者ツールのNetworkタブでProtocol列を表示するとh2h3が確認できる。初回アクセスはAlt-Svcを受け取る前なのでh2のまま、2回目以降のアクセスでh3に切り替わるのが正常な挙動だ。

まとめ

HTTP/2は新記法のhttp2 on;への移行、HTTP/3はビルド要件とUDPのファイアウォール開放が最初の関門になる。特にHTTP/3は設定ミスよりも「そもそもUDPが通っていない」「nginxにQUICモジュールが入っていない」で詰まることが多いので、順番に一つずつ確認していくのが結局早道だと思う。

読んで頂いて有り難うございます!