HTTP/3対応のNginxをUbuntu 20.04で構築する（Vagrant版）

### 概要

「[Welcome to the demo site for nginx-quic](https://quic.nginx.org/)」を参考にHTTP/3に対応したNginxの構築を行います。
※既に同じような記事がたくさん書かれていますが、微妙に構築手順に差異があったので自分用としてまとめてみました。

### 動作環境
- ホストOS: macOS Monterey 12.0.1(Intel Core i7)
- Vagrant: 2.2.19
- VirtualBox: 6.1.30
- ゲストOS: Ubuntu 20.04 LTS
- HTTP/3対応のwebサーバー: Nginx 1.21.5
- HTTP/3対応のwebクライアント: curl 7.82.0-DEV

### 仮想環境の構築

「Ubuntu 20.04 LTS」の Vagrantfile を作成します。

```
$ vagrant init ubuntu/focal64
```

オリジナルの Vagrantfile をコピーします。

```
$ cp Vagrantfile Vagrantfile.orig
```


Vagrantfile の最下行に以下を追記します。（メモリのサイズに関しては環境に応じて適宜設定してください）

```
% diff Vagrantfile Vagrantfile.orig
70,72d69
<   config.vm.provider "virtualbox" do |vb|
<     vb.memory = "8192"
<   end
```


仮想環境を起動します。

```
$ vagrant up
```


仮想環境にSSHログインします。

```
$ vagrant ssh
```

各パッケージを更新します。

```
$ sudo apt -y update
$ sudo apt -y upgrade
```

### BoringSSLをインストール

「[Experimental QUIC support for nginx](https://quic.nginx.org/readme.html)」の「2. Installing」を参照すると、「You will need a BoringSSL library that provides QUIC support」とあるので、まずは [BoringSSL](https://boringssl.googlesource.com/boringssl/) をインストールします。
BoringSSL は「BoringSSL is a fork of OpenSSL that is designed to meet Google's needs.」とのこと。

「[Building BoringSSL](https://boringssl.googlesource.com/boringssl/+/HEAD/BUILDING.md)」の[Build Prerequisites](https://boringssl.googlesource.com/boringssl/+/HEAD/BUILDING.md#build-prerequisites)を参照すると以下のような記述があるため、必要なパッケージをそれぞれインストールします。

> CMake 3.5 or later is required.

CMake が必要。 → cmake をインストールします。

> Building with Ninja instead of Make is recommended

ビルド時は Make よりも Ninja がオススメ。 → ninja-build をインストールします。（あとの手順で make も必要なので一緒にインストールします）

> C and C++ compilers with C++11 support are required. 

C と C++ のコンパイラが必要。 → gcc と g++ をインストールします。

> The most recent stable version of Go is required. 

Goの安定版が必要。 → golang をインストールします。

> the tests have an optional libunwind dependency

テストが libunwind に依存。  → libunwind-dev をインストールします。


また、 [BoringSSL](https://boringssl.googlesource.com/boringssl) のページの「Clone this repo」を見ると、Gitでリポジトリを管理しているようなので、 git も一緒にインストールします。

```
$ sudo apt -y install \
cmake \
ninja-build \
make \
gcc \
g++ \
golang \
libunwind-dev \
git
```

「[Building BoringSSL](https://boringssl.googlesource.com/boringssl/+/HEAD/BUILDING.md)」の[Building](https://boringssl.googlesource.com/boringssl/+/HEAD/BUILDING.md#building)の手順のとおりにインストールを行います。

```
$ cd ~
$ git clone https://boringssl.googlesource.com/boringssl
$ cd boringssl
$ mkdir build && cd $_
$ cmake -GNinja ..
$ sudo ninja
```

インストール後にテストを行う場合は「[Running Tests](https://boringssl.googlesource.com/boringssl/+/HEAD/BUILDING.md#running-tests)」を参考に行います。

```
$ cd ..
$ sudo ninja -C build run_tests
```

### Nginxをインストール

「[Experimental QUIC support for nginx](https://quic.nginx.org/readme.html)」の「2. Installing」を参照すると hg コマンドを利用しているので、 mercurial をインストールします。

```
$ sudo apt -y install mercurial
```

「2. Installing」の手順に沿ってインストールを進めると、以下のようなエラーが発生する場合があります。

```
./auto/configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.
```

```
./auto/configure: error: the HTTP gzip module requires the zlib library.
You can either disable the module by using --without-http_gzip_module
option, or install the zlib library into the system, or build the zlib library
statically from the source with nginx by using --with-zlib=<path> option.
```

インストールを進める上で「PCRE library」と「zlib library」が必要とのことなので、事前に libpcre3-dev と zlib1g-dev をインストールしておきます。

```
$ sudo apt -y install libpcre3-dev zlib1g-dev
```

「[Experimental QUIC support for nginx](https://quic.nginx.org/readme.html)」の手順のとおりにインストールを行います。

```
$ cd ~
$ hg clone -b quic https://hg.nginx.org/nginx-quic
$ cd nginx-quic
$ ./auto/configure \
--with-debug \
--with-http_v3_module \
--with-cc-opt="-I../boringssl/include" \
--with-ld-opt="-L../boringssl/build/ssl  \
-L../boringssl/build/crypto"
$ make
$ sudo make install
```

nginx コマンドの「-V」オプションでインストール時のオプション等を確認します。

```
$ /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.21.5
built by gcc 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04) 
built with OpenSSL 1.1.1 (compatible; BoringSSL) (running with BoringSSL)
TLS SNI support enabled
configure arguments: --with-debug --with-http_v3_module --with-cc-opt=-I../boringssl/include --with-ld-opt='-L../boringssl/build/ssl -L../boringssl/build/crypto'
```

### SSL証明書の作成

Homebrew を利用するため https://brew.sh/ の手順に従いインストールします。

```
$ cd ~
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
・
・
==> Next steps:
- Run these two commands in your terminal to add Homebrew to your PATH:
    echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/vagrant/.profile
    eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
- Install Homebrew's dependencies if you have sudo access:
    sudo apt-get install build-essential
  For more information, see:
    https://docs.brew.sh/Homebrew-on-Linux
- We recommend that you install GCC:
    brew install gcc
- Run brew help to get started
- Further documentation:
    https://docs.brew.sh
```

インストールの最後に表示されている「Next steps」の指示通りコマンドを実行します。

```
$ echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/vagrant/.profile
$ eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
```

brew コマンドが実行できるか確認します。

```
$ brew -v
Homebrew 3.3.10
Homebrew/homebrew-core (git revision 9188a0a50d5; last commit 2022-01-17)
```

mkcert をインストールします。

```
$ brew install mkcert
```

自己認証局を設定します。

```
$ mkcert -install
Created a new local CA 💥
The local CA is now installed in the system trust store! ⚡️
```

mkcert へのパスが通っていない root ユーザーで実行するため事前にフルパスを確認します。

```
$ which mkcert
/home/linuxbrew/.linuxbrew/bin/mkcert
```

SSL証明書を作成します。

```
$ sudo mkdir /usr/local/nginx/conf/certs && cd $_
$ sudo /home/linuxbrew/.linuxbrew/bin/mkcert localhost

Created a new certificate valid for the following names 📜
 - "localhost"

The certificate is at "./localhost.pem" and the key at "./localhost-key.pem" ✅

It will expire on 17 April 2024 🗓
```

SSL証明書がカレントディレクトリに作成されていることを確認します。

```
$ ls -l localhost*
-rw------- 1 root root 1704 Jan 17 03:46 localhost-key.pem
-rw-r--r-- 1 root root 1464 Jan 17 03:46 localhost.pem
```

### Nginxの設定ファイルの準備

[Experimental QUIC support for nginx](https://quic.nginx.org/readme.html)の「3. Configuration」を参考に nginx.conf を修正します。

オリジナルの nginx.conf をコピーします。

```
$ sudo cp /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.conf.orig
```

nginx.conf に以下を追記します。

```
$ diff /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.conf.orig 
117,137d116
<     log_format quic '$remote_addr - $remote_user [$time_local] '
<                     '"$request" $status $body_bytes_sent '
<                     '"$http_referer" "$http_user_agent" "$http3"';
< 
<     access_log logs/access.log quic;
< 
<     server {
<         # for better compatibility it's recommended
<         # to use the same port for quic and https
<         listen 8443 http3 reuseport;
<         listen 8443 ssl;
< 
<         ssl_certificate     certs/localhost.pem;
<         ssl_certificate_key certs/localhost-key.pem;
<         ssl_protocols       TLSv1.3;
< 
<         location / {
<             # required for browsers to direct them into quic port
<             add_header Alt-Svc 'h3=":8443"; ma=86400';
<         }
<     }
```

nginx.conf のチェックを行います。

```
$ sudo /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
```

systemd経由でNginxを起動するため、「[NGINX systemd service file](https://www.nginx.com/resources/wiki/start/topics/examples/systemd/)」を参考に制御用のファイルを作成します。

```
$ cat /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=/usr/local/nginx/logs/nginx.pid
ExecStartPre=/usr/local/nginx/sbin/nginx -t
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target
```

### Nginxの起動

以下のコマンドでNginxを起動します。

```
$ sudo systemctl start nginx.service
```

ポート8443がLISTEN状態であることを確認します。

```
$ ss -lnt |grep ':8443'
LISTEN   0        511              0.0.0.0:8443          0.0.0.0:* 
```

標準でインストールされている curl コマンドでレスポンスが返ってくるか確認します。

```
$ curl -I https://localhost:8443
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Mon, 17 Jan 2022 04:25:35 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Mon, 17 Jan 2022 03:04:29 GMT
Connection: keep-alive
ETag: "61e4dcbd-267"
Alt-Svc: h3=":8443"; ma=86400
Accept-Ranges: bytes
```

### HTTP/3に対応した curl コマンドをインストール

curl のリポジトリの「[HTTP3 (and QUIC)](https://github.com/curl/curl/blob/master/docs/HTTP3.md#http3-and-quic)」を参考にHTTP/3に対応した curl コマンドのインストールを行います。

今回は「[quiche version](https://github.com/curl/curl/blob/master/docs/HTTP3.md#quiche-version)」の手順に従ってインストールを行います。

必要なパッケージをインストールします。

```
$ sudo apt -y install cargo autoconf libtool
```

quiche と BoringSSL のインストールを行います。

```
$ cd ~
$ git clone --recursive https://github.com/cloudflare/quiche
$ cd quiche
$ cargo build --package quiche --release --features ffi,pkg-config-meta,qlog
$ mkdir quiche/deps/boringssl/src/lib
$ ln -vnf $(find target/release -name libcrypto.a -o -name libssl.a) quiche/deps/boringssl/src/lib/
```

curl のインストールを行います。（configure実行時に「HTTP3 enabled but marked EXPERIMENTAL. Use with caution!」というメッセージが表示されますので、あくまで検証用途としての利用に留めておいた方がよさそうですね）

```
$ cd ..
$ git clone https://github.com/curl/curl
$ cd curl
$ autoreconf -fi

$ ./configure LDFLAGS="-Wl,-rpath,$PWD/../quiche/target/release" --with-openssl=$PWD/../quiche/quiche/deps/boringssl/src --with-quiche=$PWD/../quiche/target/release
  ・
  ・
  WARNING:  HTTP3 enabled but marked EXPERIMENTAL. Use with caution!

$ make
$ sudo make install
```

src ディレクトリ配下にHTTP/3対応の curl コマンドがインストールされたことを確認します。

```
$ ./src/curl --version
curl 7.82.0-DEV (x86_64-pc-linux-gnu) libcurl/7.82.0-DEV BoringSSL zlib/1.2.11 quiche/0.10.0
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: alt-svc AsynchDNS HSTS HTTP3 HTTPS-proxy IPv6 Largefile libz NTLM NTLM_WB SSL UnixSockets
```

### HTTP/3対応の curl コマンドでHTTPリクエストを実行

先程インストールしたHTTP/3に対応した curl コマンドに「--http3」オプションを付与してリクエストを行うと、「HTTP/3 200」という結果が返ってくることが確認できました。

```
$ ./src/curl -I https://localhost:8443 --http3
HTTP/3 200
server: nginx/1.21.5
date: Mon, 17 Jan 2022 05:08:36 GMT
content-type: text/html
content-length: 615
last-modified: Mon, 17 Jan 2022 03:04:29 GMT
etag: "61e4dcbd-267"
alt-svc: h3=":8443"; ma=86400
accept-ranges: bytes
```

### 参考URL

- [Our Roadmap for QUIC and HTTP/3 Support in NGINX](https://www.nginx.com/blog/our-roadmap-quic-http-3-support-nginx/)
- [QUICおよびHTTP/3対応のNGINXプレリリース版のご紹介](https://www.nginx.co.jp/blog/introducing-technology-preview-nginx-support-for-quic-http-3/) （[英語版](https://www.nginx.com/blog/introducing-technology-preview-nginx-support-for-quic-http-3/)）
- [HTTP/3とQUICについてざっくり解説](https://qiita.com/kazenomachi/items/53a4642d35fc2182fa1a)
- [HTTP/3が出るらしいという話を雑に書く](https://qiita.com/inductor/items/61b824289ac9b2f11b3e)
- [HTTP/3とQUICはなぜ必要になり、どのように標準化されてきたのか？ 現代のプロトコル設計とインターネットの課題](https://eh-career.com/engineerhub/entry/2021/11/29/130000/)
- [HTTP/3の基盤となる「QUICプロトコル」の標準化プロセスが完了、IETFの「RFC 9000」として](https://www.publickey1.jp/blog/21/http3quicietfrfc_9000.html)
- [GoogleによるOpenSSLのfork、BoringSSLを試す。](https://jovi0608.hatenablog.com/entry/20140729/1406624449)
- [【QUIC対応】HTTP/3対応版nginxを使えるようにBoringSSLのビルドからやってみた](https://www.mimoex.net/nginx-boringssl-ninja-http3/)
- [NginxのHTTP/3を試す (2020年6月)](https://asnokaze.hatenablog.com/entry/2020/06/11/133357)
- [nginx-quic をビルドしてみた](https://hawksnowlog.blogspot.com/2021/05/getting-started-nginx-quic.html)
- [HTTP/3 対応の nginx を作る](https://qiita.com/keys/items/bf492ef543fde3d8d822)
- [QUIC及びHTTP/3対応のNGINXサーバーを構築](https://qiita.com/girlfellfromsky/items/a0a797b76b5aa35cca68)
- [Experiment with HTTP/3 using NGINX and quiche](https://blog.cloudflare.com/experiment-with-http-3-using-nginx-and-quiche/)
- [How to use HTTPS for local development](https://web.dev/how-to-use-local-https/)
- [開発環境をhttps化するmkcertの仕組み](https://qiita.com/k_kind/items/b87777efa3d29dcc4467)
- [WEB+DB PRESS Vol.123](https://gihyo.jp/magazine/wdpress/archive/2021/vol123)