HTTP/2を実際に使用するためのサーバー設定

今日では主要なWebブラウザのほとんどがHTTP/2をサポートしており、Webサーバーさえ対応すればHTTP/2が利用できる状況となっている。本記事ではApache HTTP ServerおよびNGINXでHTTP/2を使うための設定について紹介する。

なお、HTTP/2の詳細については前編で紹介しているので、そちらを参照して欲しい。

WebサーバーのHTTP/2対応状況

今日では多くのWebサーバーがHTTP/2をサポートしており、広く使われているApache HTTP ServerやNGINXでも比較的容易にHTTP/2が利用できる状況となっている。また、WindowsやWindows Serverなどで提供されているWebサーバーであるInternet Information Services(IIS)についても、Windows 10およびWindows Server 2016でHTTP/2が利用できるようになっている。

本記事ではこれらのうち、Apache HTTP ServerおよびNGINXでHTTP/2を利用するための設定について紹介する。

Apache HTTP Server 2.4.17以降でHTTP/2を使う

Apache HTTP Serverでは、バージョン2.4.17以降で「mod_http2」というモジュールを導入することでHTTP/2を利用可能になる。ただし、多くのLinuxディストリビューションにおいてApache HTTP Serverは標準パッケージとして提供されているものの、提供されているバージョンが2.4.16以前であったり、mod_http2が無効化されていたりする場合が多い。

たとえばRed Hat Enterprise Linux(RHEL) 7やその互換環境であるCentOS 7では、提供されているApache HTTP Serverのバージョンが2.4.6であるため、そのままではHTTP/2は利用できない。Debianについても、現在の安定版であるDebian 8系(jessie)で提供されているバージョンは2.4.10なので同じくHTTP/2はサポート対象外だ。

UbuntuについてはUbuntu 16.04LTS(xenial)や16.10(yakkety)でバージョン2.4.18が提供されているものの、無効の設定でビルドされているため、こちらもそのままではHTTP/2は利用できない。

いっぽうFedoraでは、Fedora 22以降でmod_http2が利用できるApache HTTP Serverが提供されており、設定ファイルを変更するだけでHTTP/2が利用できる。

また、Dockerを利用するという選択肢もある。現在Dockerの公式リポジトリで提供されているhttpdコンテナの2.4系(latestやalpineを含む)ではmod_http2サポートが有効になっており、こちらも設定ファイルを用意するだけでHTTP/2が利用可能だ。

公式にHTTP/2対応版のApache HTTP Serverが提供されていない環境においては、独自にビルドを行って利用するという手段もある。具体的にはHTTP/2を実装したライブラリである「libnghttp2」ライブラリを用意したうえで、「--enable-http2」オプション付きでビルドを行えば良いのだが、アップデート時の手間などを考えるとあまりおすすめはできない。

Apache HTTP ServerでHTTP/2を利用する際の注意点

Apache HTTP Serverでは、HTTP/2を有効にすると無効の場合と比べてより多くのスレッドやメモリが必要となる。特に多くのリクエストを処理するようなサーバーではこれによる影響が大きくなる可能性があるため、導入の際には十分に注意したい。

また、HTTP/2を有効にしているサイトでワイルドカード証明書(「*.example.com」のように複数のサブドメインを対象にしている証明書)を利用し、かつバーチャルホストを使って1つのIPアドレスでそれらを運用している場合も注意が必要だ。

こういったケースでは、HTTP/2では対象のサブドメイン間でコネクションを使い回すため、各サブドメインに対しすべて同じSSL設定でアクセスを行うようになる。そのため、このような場合は各バーチャルホストのSSL関連設定をそろえておく必要がある。

Apache HTTP ServerでHTTP/2を利用するための設定

前述のとおり、Apache HTTP ServerでHTTP/2を利用するためにはmod_http2というモジュールが必要となる。このモジュールが利用できる環境であれば、このモジュールを読み込むよう設定ファイルを記述し、さらにバーチャルホストごとにHTTP/2を有効にするような設定を追加すれば良い。また、ほとんどのWebブラウザにおいてHTTP/2はhttps上でのみサポートされているので、SSL/TLS関連の設定も必要になる。

たとえばFedora 25の場合、デフォルトでmod_http2がロードされるよう設定されている(/etc/httpd/conf.modules.d/00-base.conf内にmod_http2をロードする設定が記載されている)ので、あとは次のようにバーチャルホストの設定でSSLを有効にし、SSL/TLS関連の設定を行った上で「Protocols」ディレクティブに「h2」を追加すれば良い。

<IfModule mod_ssl.c>
<VirtualHost *:443>
        ServerName <ホスト名>
        ServerAdmin <管理者メールアドレス>

        SSLEngine on
        SSLCertificateFile <SSLサーバー証明書ファイルのパス名>
        SSLCertificateKeyFile <SSL秘密鍵ファイルのパス名>
        
        :以下、SSL/TLS関連の設定
        

        Protocols h2 http/1.1
        DocumentRoot /var/www/html/
        <Directory /var/www/html/ >
                Options Indexes FollowSymLinks MultiViews
                AllowOverride FileInfo AuthConfig
                Order allow,deny
                allow from all
        </Directory>
</VirtualHost>
</IfModule>

ここでは「Protocols」ディレクティブに「h2 http/1.1」と記載することで、HTTP/2とHTTP/1.1の両方を有効にしている。

なお、SSL/TLS関連の設定については環境や使用するSSL証明書に応じて適切な設定が必要だ。また、無料でSSL証明書を発行できる「Let's Encrypt」を利用する場合、証明書取得ツールであるcertbotが設定ファイルを自動で生成してくれるので、それをベースにすると良いだろう。その場合、作成された設定ファイル内の「Protocols」部分に「h2」を追加するだけでHTTP/2が利用できるようになる。

設定ファイルを作成/編集したら、httpdを再起動することでHTTP/2が有効になる。

DockerのhttpdコンテナでHTTP/2を使う

Dockerが公式に提供している「httpd」コンテナではHTTP/2サポートが有効になっている。同梱されているデフォルトの設定ファイルではHTTP/2は有効にされていないため別途設定ファイルを用意する必要はあるが、これを利用すれば簡単にHTTP/2に対応したWebサーバーを構築できる。これらの設定についても紹介しておこう。

Docker公式のhttpdコンテナはバージョンやベースとするコンテナが異なるいくつかのバリエーションがあるが、HTTP/2が利用できるのはバージョン2.4系のコンテナだ。具体的には、本記事執筆時点では以下のコンテナがHTTP/2に対応している。

httpd
httpd:latest
httpd:2.4.25
httpd:2.4
httpd:2
httpd:2.4.25-alpine
httpd:2.4-alpine
httpd:2-alpine
httpd:alpine

これらのコンテナでは、Apache HTTP Serverの設定ファイルとしてコンテナ内の/usr/local/apache2/conf/httpd.confを使用するようになっている。この設定ファイルを変更するには、Dockerホスト上に設定ファイルを用意してコンテナの起動時にこのパスにマウントするか、これらコンテナをベースにして新しいコンテナを作成し、用意した設定ファイルで上書きすれば良い。また、HTTP/2ではSSLが事実上必須となっているので、SSL証明書の設定も必要となる。

設定ファイルをコンテナの起動時にコンテナ内にマウントする場合、以下のようなオプションを指定してコンテナを起動すれば良い。

docker run --rm -ti -v <Webサーバーが公開するファイルが含まれるディレクトリ>:/usr/local/apache2/htdocs \
  -v <用意した設定ファイル>:/usr/local/apache2/conf/httpd.conf \
  -v <SSL証明書ファイル>:<証明書ファイルの格納先パス> \
  -v <SSL秘密鍵ファイル>:<秘密鍵ファイルの格納先パス> \
  -p 80:80 -p 443:443 <コンテナ名>

たとえば、公開するファイルがDockerホスト上の/var/http2test/htdocsディレクトリに格納されており、設定ファイルが/var/http2test/httpd.conf、SSL証明書およびSSL公開鍵ファイルがそれぞれ/var/http2test/fullchain.pemおよび/var/http2test/privkey.pemだった場合、実行するコマンドは以下のようになる。

# docker run --rm -ti -v /var/http2test/htdocs:/usr/local/apache2/htdocs \
  -v /var/http2test/httpd.conf:/usr/local/apache2/conf/httpd.conf \
  -v /var/http2test/fullchain.pem:/usr/local/apache2/conf/fullchain.pem \
  -v /var/http2test/privkey.pem:/usr/local/apache2/conf/privkey.pem 
  \-p 80:80 -p 443:443 httpd:latest

また、設定ファイルについては、基本的にはデフォルトで用意されているものに下記の内容を追加すればOKだ。なお、SSL関連の設定は使用する証明書に応じて適宜変更が必要だ。ここではLet's Encryptで取得したSSL証明書を利用する場合の設定としている。

# ↓SSLを利用するためのmod_sslモジュールとHTTP/2を利用するためのmod_http2モジュールをロード
LoadModule ssl_module modules/mod_ssl.so
LoadModule http2_module modules/mod_http2.so

# ↓HTTPSでのドキュメントルート等を設定
<IfModule mod_ssl.c>
Listen 443
<VirtualHost *:443>
        ServerName <ホスト名>
        ServerAdmin <管理者メールアドレス>

        Protocols h2 http/1.1
        DocumentRoot /usr/local/apache2/htdocs/
        <Directory /usr/local/apache2/htdocs/ >
                Options Indexes FollowSymLinks MultiViews
                AllowOverride FileInfo AuthConfig
                Order allow,deny
                allow from all
                Require all granted
        </Directory>

        # ↓SSLを有効化する設定
        SSLEngine on
        SSLCertificateFile /usr/local/apache2/conf/fullchain.pem
        SSLCertificateKeyFile /usr/local/apache2/conf/privkey.pem
        SSLProtocol             all -SSLv2 -SSLv3
        SSLCipherSuite          ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES25\
6-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RS\
A-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-R\
SA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256\
:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PS\
K:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
        SSLHonorCipherOrder     on
        SSLOptions +StrictRequire

</VirtualHost>
</IfModule>

また、httpdコンテナを元に新たなコンテナを作成する場合は、設定ファイルとSSL証明書ファイルおよびSSL公開鍵ファイル、Webサーバー上で公開するファイルなどをコンテナ内にコピーすれば良い。その場合のDockerfileは以下のようになる。

FROM httpd:latest

COPY httpd.conf /usr/local/apache2/conf/httpd.conf
COPY fullchain.pem /usr/local/apache2/conf/fullchain.pem
COPY privkey.pem /usr/local/apache2/conf/privkey.pem
COPY htdocs /usr/local/apache2/htdocs

HTTP/2で通信を行ってみる

Webサーバー側でHTTP/2を有効にしたら、続いてWebブラウザでそのサイトにアクセスし、実際にHTTP/2で通信が行われているかを確認してみよう。HTTP/1.1でもHTTP/2でもWebブラウザ上での表示上は何も変化はないため、確認にはデベロッパーツールなどを利用する必要がある。

Google Chromeでは、「デベロッパーツール」の「Network」タブで使用しているプロトコルを確認できる。このタブを開いた状態でページをロードするとページの構成ファイルやその情報が一覧表示されるのだが、そのヘッダー部分を右クリックし、表示されるコンテクストメニューで「Protocol」にチェックを入れると、ページ内の各コンテンツがそのプロトコルでロードされているかを確認できる(図1)。

図1 Google Chromeの「Network」タブで使用しているプロトコルを確認できる
図1 Google Chromeの「Network」タブで使用しているプロトコルを確認できる

また、Firefoxでも同様に開発ツールの「ネットワーク」タブでファイルごとに使用しているプロトコルを確認できる(図2)。

図2 Firefoxでは開発ツールの「ネットワーク」タブで使用しているプロトコルを確認できる
図2 Firefoxでは開発ツールの「ネットワーク」タブで使用しているプロトコルを確認できる

サーバープッシュを使う

Apache HTTP Serverのmod_http2では、リクエストされていないファイルをWebサーバー側が自動的に判断してクライアントに送信するサーバープッシュ機能もサポートされており、特に設定がない場合はデフォルトで有効になる。mod_http2のサーバープッシュ機能では、レスポンスヘッダ内に次のような「Link」ヘッダーが存在した場合、そこで指定されたファイルをサーバープッシュでクライアントに送信するようになっている。

Link <<対象のファイルパス>>;rel=preload

Linkヘッダーを出力する方法にはいくつかあるが、簡単なのはApache HTTP Serverの設定ファイル内で利用できる「Header」ディレクティブを利用する方法だ。たとえば、「/index.html」というファイルに対するリクエストに対し「/css/foo.css」というファイルをサーバープッシュで送信したい場合、設定ファイル内に以下のように記述すれば良い。

<Location /index.html>
    Header add Link "</css/foo.css>;rel=preload"
</Location>

そのほか、mod_http2ではいくつかの設定パラメータも用意されている。これらについてはmod_http2のドキュメントHTTP/2 guideを参照してほしい。

NGINX 1.9.5以降でHTTP/2を使う

高速なWebサーバーとして近年シェアを増やしているNGINXでは、バージョン1.9.5でHTTP/2サポートが追加された。

NGINXにはオープンソース版と商用版(NGINX Plus)があるが、本記事執筆時点ではオープンソース版でのHTTP/2サポートは基本的な機能のみとなっており、サーバープッシュは商用版のみでのサポートとなっている。

また、Apache HTTP Serverの場合と同様、ディストリビューションによっては公式パッケージとして提供されているもののバージョンが古く、HTTP/2に対応していないケースもある。たとえばDebianの最新安定版であるjessieで提供されているNGINXはバージョン1.6.2なので非対応だ。いっぽう、Ubuntu 16.04LTS(xenial)以降ではNGINX 1.10が提供されている。また、Red Hat Enterprise Linux 7やCentOS 7では拡張リポジトリであるEPELにてNGINX 1.10が提供されている。もちろんFedoraでもFedora 24以降でNGINX 1.10が利用可能だ。

ただし、TLS接続でHTTP/2を利用する場合、使用するOpenSSLライブラリがALPN(Application-Layer Protocol Negotiation)をサポートしている必要がある。Red Hat Enterprise Linux 7やCentOS 7で提供されているOpenSSL 1.0.1系ではこの機能がサポートされていないため、TLS接続ではHTTP/2が利用できない。

また、ビルド時の設定によってはNGINX 1.10以降でもHTTP/2サポートが有効になっていない場合があるので注意したい。その確認方法だが、「nginx -V」コマンドを実行した際にその出力に「--with-http_v2_module」が含まれていればHTTP/2サポートが有効となっている。たとえば次の例はFedora 25での実行結果だが、HTTP/2サポートが有効になっていることが分かる。

$ nginx -V
nginx version: nginx/1.10.2
built by gcc 6.2.1 20160916 (Red Hat 6.2.1-2) (GCC)
built with OpenSSL 1.0.2j-fips  26 Sep 2016 (running with OpenSSL 1.0.2k-fips  26 Jan 2017)
TLS SNI support enabled
configure arguments: --prefix=/usr/share/nginx (中略) --with-http_v2_module (後略)

筆者が確認したところ、Fedora 25のほかCentOS 7(EPEL 7)、Ubuntu 16.04 LTS(xenial)でもHTTP/2サポートは有効となっていた。

NGINXでHTTP/2を有効にする設定

NGINXでは、設定ファイルの「listen」ディレクティブに「http2」を追加することでHTTP/2を有効にできる。前述のとおり、ほとんどのWebブラウザではHTTPSでしかHTTP/2を利用できないので、通常は443番ポートでの待ち受け設定部分にこれを追加することになる。

今回はHTTP/2に対応したサイト設定として、次のような設定ファイルを用意した。

server {
    listen       443 ssl http2 default_server;
    listen       [::]:443 ssl http2 default_server;
    server_name  <ホスト名>;
    root         /usr/share/nginx/html;

    ssl_certificate "<SSLサーバー証明書ファイルのパス名>";
    ssl_certificate_key "<SSL秘密鍵ファイルのパス名>";

    ssl_session_cache shared:le_nginx_SSL:1m;
    ssl_session_timeout 1440m;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;

    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256 EDH-RSA-DES-CBC3-SHA";

    # Load configuration files for the default server block.
    include /etc/nginx/default.d/*.conf;

    location / {
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }
}

TLS関連の設定については適宜使用する証明書などに応じて変更して欲しいが、すでにTLSでの設定が済んでいる環境であれば、基本的には「listen」ディレクティブに「http2」を追加するだけで対応できる。

設定が完了したら、Webブラウザでアクセスを行って実際にHTTP/2でアクセスされているかを確認しておこう。

そのほか、NGINXにおけるHTTP2関連の設定パラメータについてはngx_http_v2_moduleのドキュメントを参照して欲しい。

HTTP/1.1とHTTP/2、サーバープッシュの比較

最後に、Google Chromeのデベロッパーツールを使い、HTTP/1.1とHTTP/2でWebサーバーとWebブラウザとのやり取りがどう変化するかを見てみよう。使用したWebサーバーはApache HTTP Server 2.4.25で、Docker上で動かしている。また、使用したWebブラウザはWindows版のGoogle Chrome 56.0.2924.87だ。

Google Chromeのデベロッパーツールでは、「Network」というタブでWebブラウザとWebサーバー間の通信状況を確認できる(図3)。

図3 デベロッパーツールの「Network」タブ
図3 デベロッパーツールの「Network」タブ

ここではファイル毎に棒グラフでWebサーバーとの通信にかかった時間が表示されており、まず細い部分がコネクションの確立にかかった時間、太い部分がコネクション確立後にリクエストおよびレスポンスを送受信するのにかかった時間を示している。さらに各グラフは次のようにそれぞれ色分けがされており、詳細な情報を知ることができる。

  • 細いグラフの白い部分(Queueing):コネクションのソケットの空き待ち
  • 細いグラフの灰色の部分(Stalled):システムなどの都合で接続処理が停止した時間
  • 細いグラフの緑色の部分(DNS Lookup):ホスト名からIPアドレスを取得するのにかかった時間
  • 細いグラフのオレンジ色の部分(Initial connection):接続完了までにかかった時間
  • 細いグラフの紫の部分(SSL):SSLコネクションの確立にかかった時間
  • 細いグラフの水色の部分(Receiving Push):サーバープッシュを受け取るための待ち時間
  • 太いグラフの灰色の部分(Request sent):リクエスト送信にかかった時間
  • 太いグラフの緑色の部分(Waiting):リクエストを送信してからレスポンスが帰ってくるまでにかかった時間
  • 太いグラフの青色の部分(Content Download):コンテンツのダウンロードが開始されてから完了するまでにかかった時間

さて、まずHTTP/1.1(非SSL)の場合だが、まずHTMLファイルをダウンロードし、そのダウンロードが完了する前にそこで指定されているCSSファイルやJavaScriptファイルをWebサーバーにリクエストしていることが分かる(図4)。Google Chromeでは同時に接続するコネクション数が6となっているが、ここではそれらを使い回してコンテンツのリクエストとダウンロードを行っていることも確認できる。

図4 非SSLのHTTP/1.1接続のネットワーク利用状況
図4 非SSLのHTTP/1.1接続のネットワーク利用状況

また、HTTP/1.1でSSLを利用した場合、SSLコネクションの確立に必要な時間(紫色の部分)が新たに増えているが、全体的な流れはSSLを使わない場合とほぼ同じだ(図5)。ちなみにHTTP/1.1では一度確立したコネクションを使い回すことができるので、グラフ上で紫色の部分は6つ(=Chromeが同時に使用するコネクション数)しかない。

図5 SSLを使ったHTTP/1.1接続のネットワーク利用状況
図5 SSLを使ったHTTP/1.1接続のネットワーク利用状況

いっぽう、HTTP/2で接続を行った場合、接続が完了するまでにかかる時間が最初の1回以外は大きく短縮されていることが分かる(図6)。また、コネクションを1つしか使わないので、SSLコネクション確立にかかった時間を示す紫色の部分も1つしかない。

図6 HTTP/2接続のネットワーク利用状況
図6 HTTP/2接続のネットワーク利用状況

また、設定ファイルに以下の内容を追加し、サーバープッシュを利用するよう設定した場合についても見てみよう。ここではindex.htmlに対してリクエストがあった際、JavaScriptファイル2つとCSSファイル2つ、合計4ファイルをサーバープッシュで送信するよう指定している。

<Location /index.html>
Header add Link "</jquery/jquery-3.1.1.min.js>;rel=preload"
Header add Link "</bootstrap/js/bootstrap.min.js>;rel=preload"
Header add Link "</css/bootstrap_2c595fb9156f8018c2705fcf5a4a69b8.css>;rel=preload"
Header add Link "</css/main_23029855cabc83e6671ba199a7b7f8b8.css>;rel=preload"
</Location>

この場合、WebブラウザはHTMLファイルのダウンロード後、サーバープッシュで送信するよう設定した4つのCSSおよびJavaScriptファイルを先行して受け取っていることが分かる(図7)。

図7 HTTP/2接続でサーバープッシュを利用した場合のネットワーク利用状況
図7 HTTP/2接続でサーバープッシュを利用した場合のネットワーク利用状況

ただし、今回の例ではサーバープッシュで受け取ったファイルに対しても、再度リクエストを送信するという挙動になっていた。そのため、全ファイルのダウンロードが完了するまでの最終的な時間はやや遅くなっている。

ちなみにFirefox 51.0.1で確認したところ、こちらではサーバープッシュで送信されたファイルの再リクエストは行われず、送信されたファイルをキャッシュして使用していることが確認できた(図8)。

図8 Firefoxでサーバープッシュを受け取った場合の挙動
図8 Firefoxでサーバープッシュを受け取った場合の挙動

急いでHTTP/2を導入する必要はないものの、今後の動向には注目

HTTP/2をサポートしたWebブラウザの普及に伴い、今日ではHTTP/2を利用しているWebサイトも増えている。たとえばGoogleやFacebookなどは全面的にHTTP/2を有効にしているほか、Twitterなども一部でHTTP/2を利用している。とはいえ、現時点ではHTTP/2の導入が必須、というわけではない。Webアプリケーションなど動的にコンテンツを生成するようなWebサイトでは、アプリケーション側での対応なども必要となるため、HTTP/2が導入しにくいケースもある。Linuxディストリビューションでのサポートもまだ十分でない。

HTTP/1.1を利用し続けることによるデメリットというのも今のところは存在しないため、その動向に注目しつつ、導入できる環境が整ったら導入する、といった方針が無難だろう。