🌐

nginxでQUIC+HTTP/3対応Webサーバを構築する

2023/03/12に公開

https://nginx.org/en/docs/quic.html

はじめに

高速動作で有名なWebサーバであるnginxですが、かなり前からQUIC+HTTP/3対応を目指していました。
これまではソースから自前でビルドが必要だったのですが、久々に確認したところビルド済みパッケージが配布(↓)されていたので、導入してみます。

Binary Packages Now Available for the Preview NGINX QUIC+HTTP/3 Implementation

https://www.nginx.com/blog/binary-packages-for-preview-nginx-quic-http3-implementation/

Prebuilt nginx-quic packages for Linux distributions

https://quic.nginx.org/packages.html

サーバの用意

対応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のインストール

ここからは公式サイトのマニュアル通りに作業するので、特に難しいことはないはずです。
マニュアルでは既存の設定をバックアップする手順がありますが、今回は新しい環境なので省略します。

  1. まず、yum-utilsをインストールします。(使ってない気もするけどとりあえず入れる。)
$ sudo dnf install yum-utils
  1. 次にレポジトリを追加します。
$ sudo vim /etc/yum.repos.d/nginx-quic.repo

repoファイルの中には以下を貼り付けます。

nginx-quic.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
  1. 追加したリポジトリからnginx-quicをインストールします。
$ sudo dnf install nginx-quic


インストールするか?、GPG keyが問題ないか?、の2回聞かれるのでどちらもyで進みます。

  1. nginxでQUIC+HTTP/3で待ち受けるよう設定します。
    まず、初期の設定ファイルは一旦読み込まないように名前を変更します。
$ sudo mv /etc/nginx/conf.d/default.conf{,.bak}

新しく、コンフィグファイルを作成します。分かりやすいように最低限で記載します。
ここでは、IPv6とIPv4 両対応とし、従来のTCP利用の接続も待ち受ける設定にします。

$ sudo vim /etc/nginx/conf.d/www.conf
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です!

  1. 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をクリックすると確認できます。
https://http3check.net/

✅ 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