nginxでQUIC+HTTP/3対応Webサーバを構築する
はじめに
高速動作で有名なWebサーバであるnginxですが、かなり前からQUIC+HTTP/3対応を目指していました。
これまではソースから自前でビルドが必要だったのですが、久々に確認したところビルド済みパッケージが配布(↓)されていたので、導入してみます。
Binary Packages Now Available for the Preview NGINX QUIC+HTTP/3 Implementation
Prebuilt nginx-quic packages for Linux distributions
サーバの用意
対応OS/アーキテクチャは以下とのことです。
- RHEL 9 and derivatives: amd64, arm64
- Ubuntu 22.04: amd64, arm64
arm64が対応しているようなので、Oracle CloudでOracle Linux 9のAmpereインスタンスに入れてみます。(Always Freeなのでインスタンス料金は無料、通信やストレージも無料枠があるので、お試し程度なら無料です。)
SSH鍵の作成云々やVCNの配置などは、ここでは省略します。
とりあえず、パブリックIPv4アドレスを割り当ててインスタンスを作成します。
ネットワーク・リスト/ネットワーク・セキュリティ・グループのイングレスルールの設定
QUICはUDP/443を使用しますので、該当プロトコルを許可リストに入れておきます。
ここでは、通常のHTTP 1.1/2形式のHTTP/HTTPS通信も許可しています。画像ではIPv4のみ許可していますが、IPv6でもサービスする場合は同様に追加します。インターネット全体に公開する際のIPv6のCIDRは::/0
です。
nginxのインストール
ここからは公式サイトのマニュアル通りに作業するので、特に難しいことはないはずです。
マニュアルでは既存の設定をバックアップする手順がありますが、今回は新しい環境なので省略します。
- まず、yum-utilsをインストールします。(使ってない気もするけどとりあえず入れる。)
$ sudo dnf install yum-utils
- 次にレポジトリを追加します。
$ sudo vim /etc/yum.repos.d/nginx-quic.repo
repoファイルの中には以下を貼り付けます。
[nginx-quic]
name=nginx-quic repo
baseurl=https://packages.nginx.org/nginx-quic/rhel/9/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
- 追加したリポジトリからnginx-quicをインストールします。
$ sudo dnf install nginx-quic
インストールするか?、GPG keyが問題ないか?、の2回聞かれるのでどちらもy
で進みます。
- nginxでQUIC+HTTP/3で待ち受けるよう設定します。
まず、初期の設定ファイルは一旦読み込まないように名前を変更します。
$ sudo mv /etc/nginx/conf.d/default.conf{,.bak}
新しく、コンフィグファイルを作成します。分かりやすいように最低限で記載します。
ここでは、IPv6とIPv4 両対応とし、従来のTCP利用の接続も待ち受ける設定にします。
$ sudo vim /etc/nginx/conf.d/www.conf
server {
listen 443 http3 reuseport; # IPv4 QUIC+http/3
listen [::]:443 http3 reuseport; # IPv6 QUIC+http/3
listen 443 http2 ssl; # IPv4 http/2 or http/1.1
listen [::]:443 http2 ssl; # IPv6 http/2 or http/1.1
server_name yourdomain.example.com; # 所持ドメイン名に変更する
root /var/www; # コンテンツ配置ディレクトリに変更する
# 証明書の配置先に変更する
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.3;
# required for browsers to direct them into quic port
add_header Alt-Svc 'h3=":443"; ma=86400';
index index.html index.htm;
}
保存したら、設定ファイルが問題ないか確かめます。
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
test is successful
が表示されたらOKです!
- nginxの起動
それではnginxを起動します。
$ sudo systemctl start nginx
$ systemctl status nginx
Active: active (running)
となっていればひとまずOKです。
netstatでも確認してみます。
$ sudo netstat -pan -A inet,inet6 | grep -v ESTABLISHED | grep nginx
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 42205/nginx: master
tcp6 0 0 :::443 :::* LISTEN 42205/nginx: master
udp 0 0 0.0.0.0:443 0.0.0.0:* 42205/nginx: master
udp6 0 0 :::443 :::* 42205/nginx: master
UDP/443で待ち受けていることが確認できますね。
確認
簡単に確認するには、以下のサイトが便利です。
Hostname欄にURLを入れてCHECKをクリックすると確認できます。
✅ QUIC is supported
✅ HTTP/3 is supported
となればOKです!
トラブルシューティング
nginxが起動できない
まずはnginxのエラーログを確認しましょう。tailコマンドで最下部を開くのが便利です。
$ sudo tail /var/log/nginx/error.log
[emerg] #: bind() to 0.0.0.0:443 failed (13: Permission denied)
このエラーが吐かれている場合は、SELinuxのポリシーによってリッスンできない状態です。QUICではUDP/443を使用しますが、もともとのポリシーではTCPしか定義されていないため、ポリシー違反で許可されない状態になっています。
SELinuxを無効にするのが手っ取り早いですが、セキュリティ上はあまりよろしくないので、ポリシーに追加してみます。
ポリシーを変更するにはsemanage
コマンドを利用しますが、入っていない場合はsudo dnf install policycoreutils-python-utils
で先にインストールしてください。
先に定義を確認してみます。
$ sudo semanage port -l | grep http
http_cache_port_t tcp 8080, 8118, 8123, 10001-10010
http_cache_port_t udp 3130
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
pegasus_http_port_t tcp 5988
pegasus_https_port_t tcp 5989
確かにUDP/443は定義されていないですね。ポリシーにUDP/443を追加します。
$ sudo semanage port -a -t http_port_t -p udp 443
-a
はポリシーの追加、-t
はタイプ、-p
はポートの定義になります。
nginxは起動できるが繋がらない
ファイアウォールで遮断されている
ファイアウォールを確認します。
$ sudo firewall-cmd --list-all
- サーバのファイアウォールが無効の場合
FirewallD is not running
- サーバのファイアウォールが有効の場合
public (active)
target: default
icmp-block-inversion: no
interfaces: enp0s3
sources:
services: dhcpv6-client ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
ファイアウォールが有効になっている場合は、HTTPSやQUICを追加しましょう。
$ sudo firewall-cmd --add-service=https --permanent
$ sudo firewall-cmd --add-port=443/udp --permanent
$ sudo firewall-cmd --reload
デフォルトのゾーンを利用している場合は、先のコマンドで追加できます。その他のゾーンを利用している場合は、--zone=public
等の引数も追加する必要があります。
$ sudo firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: enp0s3
sources:
services: dhcpv6-client https ssh
ports: 443/udp
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
追加できました。
ホスティングサービス側で遮断されている
Oracle Cloudでは冒頭のネットワーク・セキュリティ・グループの設定に追加していますが、その他クラウドやVPSでも遮断する機能を利用していたり、デフォルトでオンになっていたりする場合は通過できるよう設定変更しましょう。
おまけ:Alt-Svcヘッダについて
nginxのコンフィグに以下を追加していますが、何に利用されるのでしょうか?
add_header Alt-Svc 'h3=":443"; ma=86400';
通常ブラウザはTCPで通信しますので、まずTCPでリクエストを投げます。このときレスポンスに上記ヘッダが含まれていると、ブラウザはHTTP/3が利用できることを把握できるため、次回以降の通信ではHTTP/3が使われるようになります。
こちらは、Google Chromeでリクエストを投げたときのDevToolsの画面です。
最初のリクエストはProtocol:h2となっているので、HTTP/2で通信しています。
HTTP/2ではコネクションを使いまわすので、途中まではHTTP/2を利用していますが、その後Protocol:h3とある通り、HTTP/3通信に切り替えていることが確認できます。
Discussion