朝から昼寝

ITや身近な小ネタをちょこちょこ整理

(図解+詳しく)複数VirtualHostの構成とマッチング動作

概要

Apache HTTP Serverで複数のVirtualHostを構成した際の動作を整理しておこうと思いました。
main serverとの関係、default VirtualHostの概要、リクエストを処理する際のマッチング等です。
本記事は、複数VirtualHost構成時のポイントをまとめたものです。
対象はhttpd2.4系です。動作確認は主にRHEL系のLinuxで行っていますが、httpdの話なのでUbuntuBSD系でも同様です。

※表記統一のため、本記事では"a virtual host"のことをconfig上の表記と同じ"VirtualHost"と記載します。

本記事の目的

  • Apache HTTP Serverで複数VirtualHostを構成した際の仕様を把握する。


目次

基本

複数VirtualHostの構成例

本記事では、以下のような構成を例に説明します。

複数VirtualHostの構成例
複数VirtualHostの構成例

  • IP address & port combination

    • 一意なIPアドレスとポート番号の組合せです。
    • VirtualHostはIPアドレスとポート番号の組合せ単位で管理されます。
    • 例では、3つの組合せを記載してあります。
      #1 192.168.0.1:443
      #2 192.168.0.1:8443 (別ポート)
      #3 192.168.0.3:443 (別IPアドレス)
      • 例えば、#1 192.168.0.1:443に該当するVirtualHostは全て、<VirtualHost 192.168.0.1:443>により定義されます。
  • VirtualHost (a virtual host)

    • <VirtualHost>ディレクティブで定義される仮想ホストです。
    • 例では、それぞれのIPアドレスとポート番号の組合せに対し、複数のVirtualHostを記載してあります。
    • IPアドレスとポート番号の組合せが同じVirtualHostが複数ある場合、ServerNameもしくはServerAliasにより識別されます。
  • main server (非VirtualHost)

    • <VirtualHost>ディレクティブ外のconfigにより定義されるサーバ設定です。グローバル設定、ベース設定と呼ぶ場合もあります。
    • main server用のIPアドレスやポートを用意してListenさせれば、VirtualHostとは別にmain serverとしてのhttpサーバが動作します。
  • リクエストに対するマッチング順序

    • Webブラウザ等のクライアントからリクエストがあった場合、大きく以下の順序でマッチングされます。
      • 1. IP address & port matching
        IPアドレスとポート番号の組合せによるマッチング
      • 2. ServerName/ServerAlias matching
        ServerName(あるいはServerAlias)によるマッチング(ネームベースのVirtualHost使用時)
  • DNSレコード構成

    • それぞれのServerName(あるいはServerAlias)に対応するDNSレコードは定義済みであることを前提とします。

ネームベースとIPベース

VirtualHostの設定において、ネームベースかIPベースかを明示的に指定する項目はありません(httpd 2.3.11以降)。端的には、VirtualHostを設定した際に、その設定方法がネームベースとIPベースのどちらかに分類されるという意味です。
特に深く意識しなくてもVirtualHostの設定自体はできますが、理解していないと複雑な構成に対応できなかったり、マニュアルを読みづらくなったりします。

ネームベースとIPベース
ネームベースとIPベース

  • IPベース
    それぞれのVirtualHostが、固有のIPアドレスとポート番号の組合せを持ちます。逆に言うと、あるIPアドレスとポート番号の組合せに対応する<VirtualHost>ディレクティブが1つだけの場合、その<VirtualHost>はIPベースです。なお"IPベース"と言いつつも、実際には"IP&ポートベース"です。
    クライアントからのリクエスト時、使用するVirtualHostを決定するためにIPアドレスとポート番号の組合せによるマッチングが行われます。

  • ネームベース
    複数のVirtualHostが同じIPアドレスとポート番号の組合せを使用します。逆に言うと、あるIPアドレスとポート番号の組合せに対応する<VirtualHost>ディレクティブ(適切にServerNameが設定されたもの)が複数ある場合、それらの<VirtualHost>はネームベースです。
    クライアントからのリクエスト時、使用するVirtualHostを決定するために、まずIPベースの場合と同様にIPアドレスとポート番号の組合せによるマッチングが行われた後、さらにServerName(あるいはServerAlias)によるマッチングが行われます。
    ネームベースは、基本的にクライアント(ブラウザ)がHostヘッダを送信することを前提とした機能です。

マッチング動作の詳細は後述します。

VirtualHostマッチング動作

Apache HTTP ServerがWebブラウザ等のクライアントからリクエストを受け取った際、以下の順序に沿ったマッチング処理により、リクエストを処理するVirtualHostが決定されます。

  • 1. IPアドレスとポート番号の組合せによるマッチング
  • 2. ServerName(あるいはServerAlias)によるマッチング(ネームベースのVirtualHost使用時)

1. IPアドレスとポート番号の組合せによるマッチング

  • クライアントからのリクエストに使用された宛先IPアドレスと宛先ポート番号の組合せに最もマッチするVirtualHostに絞り込まれます。

    • 絞り込まれたVirtualHostが1つのみである場合(IPベース)、そのVirtualHostがリクエストを処理します。これでVirtualHostのマッチング処理は終了です。
    • 絞り込まれたVirtualHostが複数ある場合(ネームベース)、続いて"2. ServerName(あるいはServerAlias)によるマッチング"が行われます。
  • 特定のIPアドレスを指定したVirtualHost(例えば<VirtualHost 192.168.0.1>)は、ワイルドカードを指定したVirtualHost(<VirtualHost *><VirtualHost _default_>)より優先的にマッチされます。

  • 例:
    クライアントからhttps://www.testnap1-2.jp/(DNSにより192.168.0.1を解決)に対するリクエストがあった場合、IP address & port combination # 1(192.168.0.1:443)に該当するVirtualHost(#1-1、#1-2、…、#1-n)に絞り込まれます。
    複数のVirtualHostにマッチしたので、ネームベースのVirtualHostとして、続いて"2. ServerName(あるいはServerAlias)によるマッチング"が行われます。

1. IPアドレスとポート番号の組合せによるマッチング
1. IPアドレスとポート番号の組合せによるマッチング

  • SSL/TLS通信(https)時の動作については、"2. ServerName(あるいはServerAlias)によるマッチング"の方で言及します。

2. ServerName(あるいはServerAlias)によるマッチング

前述の"1. IPアドレスとポート番号の組合せによるマッチング"により、絞り込まれたVirtualHostが複数ある場合(ネームベース)の動作です。

  • (重要)<VirtualHost>の記載順とdefault VirtualHost

    • このマッチングでは、configの記載順に<VirtualHost>が参照されるので、記載順が重要です。
    • あるIPアドレスとポート番号の組合せに対応するVirtualHostのうち、最初に記載されたVirtualHostが、そのIPアドレスとポート番号の組合せにおけるdefault VirtualHostとなります。
      • それ以外のVirtualHostはnon-default VirtualHostです。
      • default VirtualHostは、所定のマッチングが成功しなかった場合等にnon-default VirtualHostより優先的に使用される動作になります。
    • IPベース(あるIPアドレスとポート番号の組合せに対応する<VirtualHost>ディレクティブが1つだけ)の場合、default VirtualHostは存在しません。
    • httpd -Sにて、現在のconfigにおいて、どのVirtualHostがdefaultになっているかを確認可能です。
    • (念のため) <VirtualHost *>ワイルドカード("*")と、このdefault VirtualHostは異なるものです。混同してはいけません。
  • SSL/TLS通信(https)については、SNI(Server Name Indication)により適切なVirtualHostマッチングを行うことができます。

    • そのTLS通信がSNI対応の場合、クライアントからSNIにより提示されたホスト名を後述のHost:ヘッダと同等に扱うことにより、適切なVirtualHostにTLSネゴシエーションを処理させることができます。
    • そのTLS通信がSNI未対応の場合、IPアドレスとポート番号の組合せに対応するVirtualHostのうち、最初に記載されたVirtualHost(default VirtualHost)がTLSネゴシエーションを処理します。この場合、意図したTLS証明書が使用されない可能性があります。この動作はSSLStrictSNIVHostCheckにより変更可能です。
    • 最近のhttpd、OpenSSL(mod_ssl)、Webブラウザであれば、基本的にSNI対応済みです。
    • (SNIについて補足) ServerName(あるいはServerAlias)によるマッチングは後述のとおりHost:ヘッダをもとに行われますが、暗号化通信はTLSネゴシエーションを終えてから開始するものなので、WebサーバはTLSネゴシエーション前にHost:ヘッダの内容を知ることができません。しかし、そもそもTLSネゴシエーションに必要な設定(使用するTLS証明書等)が個々の<VirtualHost>内に定義されている場合、TLSネゴシエーションのために、使用すべきVirtualHostの特定が必要になる、というデッドロックのような状態に陥ってしまいます。これを解決するためのTLS拡張がSNIであり、クライアントはTLSネゴシエーション時にアクセス先のホスト名をサーバに伝えることができ、サーバはそれに基づいて適切なTLS設定(今回だとVirtualHost)を選択することができます。
  • リクエストがHost:ヘッダを含む場合、その値に一致するServerNameもしくはServerAliasを持つ"最初の"VirtualHostにマッチし、そのVirtualHostがリクエストを処理します。

    • ワイルドカードや、ServerName/ServerAliasを区別してマッチする優先順位が変わることはありません("1. IPアドレスとポート番号の組合せによるマッチング"との違い)。
    • リクエストに対して一致するServerNameもしくはServerAliasを持つVirtualHostが見つからなかった場合、default VirtualHostがリクエストを処理します。
    • リクエストにHost:ヘッダが無い場合も、default VirtualHostがリクエストを処理します(HTTP/1.0等)。
    • Host:ヘッダにポート番号が記載されていたとしても、このマッチング処理には使用されません。実際にクライアントがリクエストを送信したポート番号を用いてマッチング処理が行われます。
  • 例:
    クライアントからhttps://www.testnap1-2.jp/に対するリクエストがあった場合、前述の"1. IPアドレスとポート番号の組合せによるマッチング"を終えた後、VirtualHost #1-2(www.testnap1-2.jp)にマッチします。
    クライアントが指定したURL(SNIおよびHostヘッダ)に一致するServerNameを持つVirtualHostが、VirtualHost #1-2(www.testnap1-2.jp)であるためです。

2. ServerName(あるいはServerAlias)によるマッチング
2. ServerName(あるいはServerAlias)によるマッチング

詳細

VirtualHostの定義位置

マニュアルに、main serverの定義は、VirtualHostの定義より先に記載すべきと記載があります。
configを読みやすくし、 configのマージ処理によりVirtualHostに影響する設定が不明確になることを防ぐためです。

例えば、メインのhttpd.confと、VirtualHostを定義したvhost.confがある場合、httpd.confの最後で、(他のIncludeより後に)Include vhost.confするといったconfig構成が考えられます。

(httpd.conf)
…
…(もろもろのmain server設定)
…
Include vhost.conf ←最後にInclude

VirtualHostに定義されていない設定は、main serverの設定が使用されます。

デフォルトのssl.confの<VirtualHost>DNS問題

apache.orgで配布されているhttpdや、RHEL系を含むRPMhttpdに同梱されているデフォルトのssl.conf(ソースの場合はhttpd-ssl.conf.in)には、<VirtualHost _default_:443>が定義されています。

他の位置に<VirtualHost _default_:443>を定義した際、ssl.confの方が記載順が先である場合には、このssl.confの<VirtualHost>が"*:443"というIPアドレスとポート番号の組合せに対するdefault VirtualHostになります。
このデフォルト設定は、以下のような意味合いがあると考えられます。

  • "*:443"というIPアドレスとポート番号の組合せに関して、SNI未対応の通信の際に使用されるVirtualHost設定(TLS設定)を用意しておく。
  • DNS問題を回避するための設定の一つ("create a server that has no pages to serve")。※main server自体がリクエストを処理するケースが無くなる

ただ、定義位置がssl.conf中にあって分かりづらい点や、default VirtualHostがあること自体を把握しておくのが煩雑だという場合には、ssl.conf中の<VirtualHost _default_:443>定義はコメントアウトし、共通的なTLS設定をmain serverに定義する方法も考えられます。
例えば、githiro/ssl.confに、"All SSL configuration in this context applies both to the main server and all SSL-enabled virtual hosts."というコメントでTLS関連設定をmain serverの設定として記載するような書き方があります(このファイルがどのような経緯で公開されたものかは分かりませんでした)。
ただし、前述のDNS問題への対処としては不十分になる点には注意が必要です。

VirtualHostの構成確認コマンド(httpd -S)

VirtualHostの構成は、httpd -Sで確認できます。 ※ServerAliasで指定したサーバ名は表示されないようです。

# httpd -S
VirtualHost configuration:
192.168.0.1:443        is a NameVirtualHost
         default server www.testnap1-1.jp (/etc/httpd/conf/xxx.conf:xx)
         port 443 namevhost www.testnap1-1.jp (/etc/httpd/conf/xxx.conf:xx)
         port 443 namevhost www.testnap1-2.jp (/etc/httpd/conf/xxx.conf:xx)
         port 443 namevhost www.testnap1-3.jp (/etc/httpd/conf/xxx.conf:xx)
192.168.0.1:8443        is a NameVirtualHost
         default server www.testnap2-1.jp (/etc/httpd/conf/xxx.conf:xx)
         port 8443 namevhost www.testnap2-1.jp (/etc/httpd/conf/xxx.conf:xx)
         port 8443 namevhost www.testnap2-2.jp (/etc/httpd/conf/xxx.conf:xx)
         port 8443 namevhost www.testnap2-3.jp (/etc/httpd/conf/xxx.conf:xx)
192.168.0.3:443        is a NameVirtualHost
         default server www.testnap1-1.jp (/etc/httpd/conf/xxx.conf:xx)
         port 443 namevhost www.testnap3-1.jp (/etc/httpd/conf/xxx.conf:xx)
         port 443 namevhost www.testnap3-2.jp (/etc/httpd/conf/xxx.conf:xx)
         port 443 namevhost www.testnap3-3.jp (/etc/httpd/conf/xxx.conf:xx)
ServerRoot: "/etc/httpd"
Main DocumentRoot: "/etc/httpd/htdocs"
Main ErrorLog: "/etc/httpd/logs/httpd-error.log"
Mutex ssl-stapling-refresh: using_defaults
Mutex rewrite-map: using_defaults
Mutex ssl-stapling: using_defaults
Mutex proxy: using_defaults
Mutex ssl-cache: using_defaults
Mutex default: dir="/etc/httpd/run/" mechanism=default
PidFile: "/var/run/httpd.pid"
Define: DUMP_VHOSTS
Define: DUMP_RUN_CFG
User: name="apache" id=48
Group: name="apache" id=48

関連ディレクティブ

VirtualHostに関連するディレクティブを記載しておきます。

  • <VirtualHost>

    • 基本的にIPアドレスとポート番号を指定します。
    • それぞれのVirtualHostには、固有のIPアドレスとポート番号の組合せ(IPベースの場合)、あるいは固有のホスト名(ServerNameと必要に応じServerAlias、ネームベースの場合)を定義すべきです。
    • <VirtualHost>ディレクティブとは別に、サービスするIPアドレスとポートは<Listen>ディレクティブで設定が必要です。

  • ServerName

    • 特に理由が無い限り、全ての<VirtualHost>ServerNameを指定すべきです(For optimal reliability and predictability)。
    • <VirtualHost>内でServerName未指定の場合、main serverのServerNameが引き継がれます。main serverにServerNameが無い場合、システムのホスト名(hostnameコマンド)が使用され、それが失敗した場合はDNS逆引き結果が使用されます。意図しないVirtualHostマッチングが生じる可能性が高くなってしまいます。
    • TLS対応のリバプロやTLSアクセラレータの背後にhttpdがいる場合、クライアントが使用するURLにある、https://スキームとポート番号を指定する必要性があります。(make sure that the server generates the correct self-referential URLs.)
  • ServerAlias

    • ネームベースのVirtualHost構成時、そのVirtualHostに対して別名を指定するために使用できます。
    • ワイルドカードを使用することもできます。(*.example.com)
    • 以下のような設定が可能です。
      ServerName www.example.com
      ServerAlias example.com *.example.com

  • ServerPath

    • 基本的に使用することはありません。
    • Hostヘッダ送信に対応していないブラウザがある環境向けにレガシーURLパスを指定する際に使用します。

  • NameVirtualHost

    • 既に非推奨、廃止済みです。設定上効果がありません。
  • SSLStrictSNIVHostCheck

    • SNI未対応クライアントにVirtualHostへのアクセスを許可するかどうかを指定します。
    • デフォルトで offであり、SNI未対応クライアントからのリクエストはdefault VirtualHostが処理します。

参考URL