LoginSignup
22
14

More than 1 year has passed since last update.

QUIC及びHTTP/3対応のNGINXサーバーを構築

Last updated at Posted at 2021-03-15

こんにちは。

Chromium系ブラウザではStable版でもQUIC及びHTTP/3通信が可能になりました。
新しい物好きとしては是非とも導入したいということでNGINXQUICプレビュー版を用いてQUIC及びHTTP/3対応サーバーを構築します。
幸いにも公式のREADMEが丁寧なので、それをなぞれば簡単に導入できます。

なお、先達が同様の記事を書かれていらっしゃいますが、今回は下記の通りNGINXが公式に発表したブランチを用いて構築します。

このQUIC + HTTP/3の実装は完全に新しいものであり、quicheプロジェクトの一部としてCloudflareが提供するパッチとは関係ありません。

ソースからビルドしますが、目指すのはapt installしたNginxと同じ使用感です。

自分がハイハイをしていた頃、(上級者の方々には当たり前のことも)細かく書いている記事が有難いと感じたことが多々あったので、細かくコメントを書いていきたいと思います。

追記

記事を書いてからしばらくたち、HTTP/3の仕様もだいぶ定まってきました。
それに伴ってnginx-quicにも変更があったので記事内容を更新しました。

  1. プロトコルの変更
    1. h3-29 => h3(2021/08/03)
    2. We support IETF QUIC version 1. Internet drafts are no longer supported.(2022/02/12)
  2. configure.sh内容変更
    1. --with-http_quic_moduleの削除(2022/02/12)
  3. 設定ファイルの変更
    1. $http3変数の削除(2021/08/03)

環境

AWS Lightsail上のDebianで行っていきます。
最近初めて使いましたが、某cherry blossomsよりお手軽で便利だと感じました。

Amazon Linux 2でも行いましたので追記しました。

Linux ip-***-***-***-*** 4.19.0-14-cloud-amd64 #1 SMP Debian 4.19.171-2 (2021-01-30) x86_64 GNU/Linux

シェバングは適宜書き換えてください。

下準備

利用するプロトコルが違うので、きちんと設定すればQUIC版Stable版のデュアルスタック(?)的なことも可能ですが、その場合はconfigureをごにょごにょする必要があります。
なんてことはない作業ですので解説はしませんが、解説がないとできない人は素直にsudo apt purge nginx nginx-full && sudo apt autoremove --purgeStable版をアンインストールしてからインストールするのがよいでしょう。
特に今回の設定ではapt経由と同じディレクトリにインストールすることを目指すので削除してから臨むのがよいと思います。
apt purgeすると従前の設定ファイルが消えるので保存しておきたい方はバックアップ(sudo mv /etc/nginx /etc/nginx.bak)をおすすめします。

必要なパッケージをインストールします。
Ninjaを使用するとBoringsslのビルドを高速化できるらしいです。

必要なものをインストールするぞい!
# Debian系
sudo apt update && sudo apt upgrade
sudo apt install git mercurial cmake make golang libunwind-dev libpcre3-dev zlib1g-dev gcc g++

# Debian系(Ninjaを使用)
#sudo apt install git mercurial cmake ninja-build golang libunwind-dev libpcre3-dev zlib1g-dev gcc g++

# RedHat系(Ninjaを使用)
#sudo yum update
#sudo yum install git hg cmake3 ninja-build golang libunwind-devel pcre-devel zlib-devel gcc gcc-c++

Boringssl

まずはGoogleさんが作ったOpenSSLのフォークをインストールします。
適当なディレクトリ(今回は/opt/nginxとします)を用意して、BoringSSLをダウンロードします。
ダウンロードしたディレクトリにあるBUILDING.MDを参考にビルドします。
Ninjaを用いたインストールが速いらしく推奨されていますが、今回はMakeでいきます。

Boringsslをインストールしていくぞい!
# ディレクトリを作る
sudo mkdir /opt/nginx
# 移動
cd /opt/nginx
# git clone
sudo git clone https://boringssl.googlesource.com/boringssl
# インストール
cd boringssl
sudo mkdir build
cd build

# [1]or[2]へ

# [1] makeでインストール
sudo cmake ..  # 何か不足していると言われればインストールする
sudo make  # ちょっと時間がかかります
# テスト
cd ..
go run util/all_tests.go # All tests passed!となればOK(時間がかかります)
cd ssl/test/runner
go test  # "PASS ok"と出ればOK(時間がかかります&メモリを食う)

# [2] Ninjaでインストール
#sudo cmake -GNinja ..  # Redhatでは"sudo cmake3 -GNinja .."
#sudo ninja  # Redhatでは"sudo ninja-build"
# テスト - Ninjaを使用した場合は二つのテストが一つのコマンドで完結します
#cd ..
#sudo ninja -C build run_tests  # Redhatでは"sudo ninja -C build run_tests"

本当はもう一種類テストがあるのですがどうやってもエラーなのと、放置しても進めるので今回はパスします。
下記備考の通り、インスタンスのメモリ不足が原因でした。

備考

インスタンスのスペックによってはコンパイル時に以下のようなエラーになることがあります。
Debianでは問題なかったのですが、Amazon linux 2でハマりました。

g++: internal compiler error: Killed (program cc1plus)
Please submit a full bug report, with preprocessed source if appropriate.
See <http://bugzilla.redhat.com/bugzilla> for instructions.

このエラーはメモリ不足が原因です、依存関係ではありません…。
や、まぁ確かにKilledとは書かれていますが…。
初めてだったので依存関係と思い込んで解決まで苦労しました。(本当に苦労しました)

メモリ不足で失敗しているので、不要なプロセスを終了してから再挑戦するか、インスタンスのスペックを上げることを検討しましょう。
私は1GBに上げて解決しました。
また、コンパイル後のテストも二つとも成功しました。

Nginx

では、いよいよエェンジンエッックスをインストールしていきます。(名前かっこいいですよね)

まずはダウンロードするぞい!
cd /opt/nginx
# git clone的な
sudo hg clone -b quic https://hg.nginx.org/nginx-quic

次にオプションを設定していきますが、今回はaptを用いてインストールした時と同じようになるようにインストールしたいと思います。
要するに/etc/nginx配下に設定ファイル諸々を、そして/usr/sbin/配下に実行ファイル、/var/log/配下にログファイル、etc...ということです。
ダイナミックモジュールは使いません。
sudo nano /opt/nginx/configure.shで下記のファイルを作成します。

/opt/nginx/configure.sh
#!/usr/bin/bash

cd nginx-quic
./auto/configure \
    --with-debug \
    --with-http_v2_module \
    --with-http_v3_module \
    --with-stream \
    --with-stream_quic_module \
    --with-http_ssl_module \
    --with-cc-opt="-I../boringssl/include"   \
    --with-ld-opt="-L../boringssl/build/ssl  \
                   -L../boringssl/build/crypto" \
    --prefix=/etc/nginx \
    --sbin-path=/usr/sbin/nginx \
    --conf-path=/etc/nginx/nginx.conf \
    --error-log-path=/var/log/nginx/error.log \
    --http-log-path=/var/log/nginx/access.log \
    --pid-path=/run/nginx.pid \
    --lock-path=/run/nginx.lock \
    --http-client-body-temp-path=/var/cache/nginx/client_temp \
    --http-proxy-temp-path=/var/cache/nginx/proxy_temp \
    --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
    --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
    --http-scgi-temp-path=/var/cache/nginx/scgi_temp \
    --user=www-data \
    --group=www-data

今回の設定で基本的なサーバー動作は可能ですが、モジュールは必要に応じて足し引きしてください。

追記(8/3)
PHPが503となる場合は--with-streamが指定されていない可能性があります。
当初、不要と思い省いていましたが、UNIXソケットへPHPを渡す際に必要になるようです。

ファイルを作成出来たらインストールを続行します。

インストールしていくぞい!
# 実行権付与
sudo chmod 744 configure.sh
# configure.sh実行
sudo -s ./configure.sh  # エラーなく進んでConfiguration summaryが出ればOK
cd nginx-quic
# インストール
sudo make  # ./objs配下にファイルができます
sudo make install  # ファイルを各所にコピーします(既に別バージョンがインストールされている場合は注意)
# 確認
sudo -s /usr/sbin/nginx -V  # インストールした内容できちんと表示されればOK

以上でインストール自体は完了です。

簡易設定

とりあえず動作確認だけできるように設定します。

nginx.conf.defaultで同じものが用意されていますので、インストール時に用意される設定ファイルはいったん削除して新しく作り直します。

  1. sudo rm -f /etc/nginx.conf
  2. sudo nano /etc/nginx.conf
/etc/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    server_tokens off;
    charset utf-8;

    include mime.types;
    default_type application/octet-stream;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    server {
        listen 80;
        listen [::]:80;
        index index.html;
        root /var/www/html;
    }
}

IPv6推進の過激派なので最小設定といえどIPv6待ち受けは必須。
最後に諸々のディレクトリなどを用意します。

ディレクトリをつくるぞい!
sudo mkdir /var/cache/nginx
sudo -u www-data mkdir /var/www  # エラーが出たら下記「エラーがでたら」を参考にして解決して下さい
sudo mv /etc/nginx/html /var/www/

systemd管理

言わずもがな、手動で起動するのは非現実的ですので設定していきます。
まずはユニットファイルを作成します。
これは公式ページにあるものを丸ぱくりです。

sudo nano /lib/systemd/system/nginx.service

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

[Install]
WantedBy=multi-user.target

ラズパイやさくらでは問題なかったのですが、PIDFileでなぜか怒られました。
forkしたPIDsystemdが自動で追跡できるので明示しなくても問題ないと判断しコメントアウトします。
それでは、ユニットファイルを読み込んで起動させていきます。

Nginxをsystemdおじさんに管理してもらうぞい!
sudo systemctl daemon-reload
sudo systemctl start nginx

エラーが出たら必要なファイルを用意するなど適宜対応してください。
問題なく起動できればブラウザででサーバーのIPアドレスを打ち込んでアクセスしてみましょう。
Welcome to nginx!というページが表示されれば成功です。

問題がなければ自動起動するようにします。

自動起動させるぞい!
sudo systemctl enable nginx

エラーがでたら

下記のようなエラーに遭遇することがあります。

nginx[xxxx]: nginx: [emerg] getpwnam("www-data") failed in ...

これは、www-dataというユーザーが存在しないと怒られているので以下のようにして追加します。
www-dataというユーザーを追加して、ホームディレクトリを/var/wwwにして、ログインを禁止しています。
passwdでパスワードを設定してはいけません

sudo useradd -d /var/www -s /usr/sbin/nologin www-data

SSL証明書取得

これはちょっと違う話になるので別記事(certbotの導入手順が新しくなっていた件)で扱います。
既に発行済みの方はそちらを利用してください。

本番設定

皆様お好みの設定があると思うので、HTTP/3及びQUICを含めて取り敢えず動く為に必要な最低限の設定だけ書いておきます。

/etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    ####
    # Basic settings
    ####

    server_tokens off;  # Hide server information
    charset utf-8;

    include mime.types;
    default_type application/octet-stream;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ####
    # HTTP/3 and QUIC minimal settings
    ####

    ssl_protocols TLSv1.3;  # TLSv1.3 is required, but you can add TLSv1.2 for compatibility
    ssl_prefer_server_ciphers off;
    #ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;  # Uncomment to disable unsafe ciphers if you use TLSv1.2

    # quic_retry on;  # Uncomment to enable address validation
    # ssl_early_data on;  # Uncomment to enable 0-RTT

    server {
        listen 443 ssl http2;  # Listen to legacy connections (HTTP/2)
        listen [::]:443 ssl http2;  # Same as above, for IPv6
        listen 443 http3 reuseport;  # Listen to cutting-edge connections!! (HTTP/3,QUIC)
        listen [::]:443 http3 reuseport;  # Same as above, for IPv6

        server_name example.com;

        index index.html index.php;
        root /var/www/example.com;

        ssl_certificate path2certs/example.com.crt;
        ssl_certificate_key path2certs/example.com.key;

        location / {
            try_files $uri $uri/ =404;
            #add_header Alt-Svc '$http3=":443"; ma=86400';  # Older version
            add_header Alt-Svc 'h3=":443"; h3-29=":443"; ma=86400';  # Tell browsers that HTTP/3 is available and its listening port
            add_header QUIC-Status $quic;  # Indicate using quic
        }
    }

    ####
    # Redirect HTTP to HTTPS
    ####

    server {
        listen 80;
        listen [::]:80;
        return 301 https://$host$request_uri;
    }
}

Nginxを触ったことがある人なら分かると思いますが、一応所々にコメントを付しておきましたので参考にしてください。
変数$http3は使用しているプロトコルが返りますので、現在であればh3-29(IETF QUIC)となります。(追記参照)
もう一つ$quicという変数も用意されており、こちらはQUICが利用されている際に文字列quicが返ります。

追記(8/3)
変数$http3は削除された模様です。
私見ですが、HTTP/3がIETFにより正式に策定されたため記述方法を変数化する必要がなくなったのかと思われます。
Googleの書き方を参考に最低限の記述量で済むように書き換えました。
最終的にはh3=":443"だけで済むようになるのではないでしょうか。
ただ、現在ではh3-29を含めないと読み込まれない事象も多々見受けられます。

要点は以下の二つです。

  • TLSv1.3を有効にする
  • listen ... http3 reuseportで待ち受ける
  • add_header Alt-Svc '$http3=":443"'で伝える

サーバーはネコでブラウザはタチだけどお互い知らないので交渉前にサーバー側からネコって自己申告するという話です。
失礼。

確認

実際にどのプロトコルで通信しているかは、デベロッパーツールのネットワークタブで表のタイトル行を右クリックしてProtocolにチェックを入れれば確認できます。
http/1.1はそのままHTTP/1.1で、h2HTTP/2h3-29HTTP/3です。

追記(8/3)
HTTP/3h3となっています。

ファーストコンタクトには適用されず(ここでAlt-svcを受け取る)、二つ目以降のコネクションからh3-29h3が適用されます。

Cache-Control: no-cacheのヘッダーが適用された場合などでキャッシュを読み込むとh3-29とはならないので、デベロッパーツールのDisable cacheにチェックを入れてリロードしてみてください。
キャッシュの削除とハード再読み込みをするとAlt-svcの情報も消えてしまうので注意してください。

add_header QUIC-Status $quic;を設定した場合はレスポンスヘッダーからQUICが使用されているかを確認できます。
QUIC-Status: quicが有れば有効、なければ無効です。

アップデート

試験版なので数日ごとに更新がありますから、手軽にアップデートをできるようにします。

sudo nano /opt/nginx/update.shで以下の内容のファイルを作成します。
Debian系Ninjaを使用しない前提で書いてあるので環境に合わせて書き直してください。

/opt/nginx/update.sh
#!/usr/bin/bash

git -C /opt/nginx/boringssl pull | grep -q 'Already up to date'
boringStatus=("${PIPESTATUS[@]}")

if [ ${boringStatus[0]} -eq 0 ]; then
    if [ ${boringStatus[1]} -gt 0 ]; then
        cd /opt/nginx/boringssl/build
        cmake ..
        cmakeStatus=$?
        make
        if [ $? -eq 0 -a $cmakeStatus -eq 0 ]; then
            echo 'BoringSSL update(s) installation has succeeded.'
            boringUpdated=0
        else
            echo 'BoringSSL update(s) installation has failed.(Installation failure)'
            boringUpdated=1
        fi
    else
        echo 'BoringSSL is already up to date.'
        boringUpdated=1
    fi
else
    echo 'BoringSSL update(s) installation has failed.(Sync failure)'
    boringUpdated=1
fi

hg --cwd /opt/nginx/nginx-quic pull -u | grep -q 'no changes found'
nginxStatus=("${PIPESTATUS[@]}")

if [ ${nginxStatus[0]} -eq 0 ]; then
    if [ ${nginxStatus[1]} -gt 0 -o $boringUpdated -eq 0 ]; then
        /opt/nginx/configure.sh
        cd /opt/nginx/nginx-quic
        make
        if [ $? -eq 0 ]; then
            systemctl stop nginx
            make install
            if [ $? -eq 0 ]; then
                systemctl start nginx
                echo 'NGINX update(s) installation has succeeded.'
                exit 0
            else
                echo 'NGINX update(s) installation has failed.(Installation failure)'
                exit 1
            fi
        else
            echo 'NGINX update(s) installation has failed.(Building failure)'
            exit 1
        fi
    else
        echo 'NGINX is already up to date.'
        exit 0
    fi
else
    echo 'NGINX update(s) installation has failed.(Sync failure)'
    exit 1
fi

exit 1

sudo chmod a+x /opt/nginx/update.shで実行権を付与して、あとは手動で実行するなり、cronで定期実行するなりすればOKです。

grepで拾うこの方法はスマートじゃなくて嫌なのですが、もう少しスマートな方法があれば教えてください。

最後に

結構簡単にHTTP/3QUICに対応したサーバーが構築できます。
あまり体感するほどではないですが、気持ちはよいです。

QUICプレビュー版のバージョンは1.19なのでaptからインストールできるようになるのは少し先の話かもしれません。
基本的には安定しているので、非商業用であれば一足先に導入してみるのもよいですね。

当方ではPHPで時折400 Bad requestが発生する問題がありますが、解決方法は未だ見つけられていません。
ご存じの方がいましたら教えてください。

追記(8/3)
アップデートで概ね解決しています。
未解決の方は、configure時に--with-stream--with-stream_quic_moduleオプションが指定されているかを確認してください。

また、OCSP staplingには非対応です。

最後までお読みいただきありがとうございました。

22
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
14