Apache(httpd)で複数のWebサイトを同一サーバーで運用するとき、中心になるのが VirtualHost と SNI である。
ここが噛み合っていないと「別ドメインの証明書が返る」「HTTPSだけ想定外のサイトが出る」「端末によって警告が出たり出なかったりする」といった事故が起きやすい。
このページでは、VirtualHostの選ばれ方と、TLSの入口で証明書を出し分けるSNIの仕組みを、Apacheの設定例と確認コマンドを交えて整理する。
- 専門用語:VirtualHost
- 解説:1台のApacheで複数サイトを提供するための「受付ルール」。ServerNameやDocumentRoot、証明書の設定などをサイト単位で切り替える。
- 身近な例:同じ建物の中に複数の窓口があり、用件に応じて窓口が変わる。
- 専門用語:Name-based VirtualHost
- 解説:同じIPアドレスでも、ホスト名(ServerName/ServerAlias)でサイトを切り替える方式。外部公開で最も一般的。
- 身近な例:同じビルだが「何課に用事か(ホスト名)」で窓口が決まる。
- 専門用語:IP-based VirtualHost
- 解説:IPアドレスを分けてサイトを切り替える方式。IPが増える分、運用は単純になりやすい。
- 身近な例:建物(IP)が別なので入口も別。
- 専門用語:SNI(Server Name Indication)
- 解説:TLSハンドシェイクの段階で「接続したいホスト名」をサーバに伝える拡張。HTTPSで証明書を出し分けるために必要。
- 身近な例:受付で先に「どの部署(ホスト名)か」を伝える。伝えないとデフォルト窓口に案内される。
- 専門用語:ServerName / ServerAlias
- 解説:VirtualHostが「どのホスト名を担当するか」を決める設定。ここが一致しないと、意図したVirtualHostが選ばれない。
- 身近な例:窓口の看板。看板の名前が違うと、別の窓口に回される。
- 専門用語:default server(デフォルトVirtualHost)
- 解説:一致するVirtualHostが見つからないときに使われる「最後の受け皿」。設定の読み込み順や定義順で決まる。
- 身近な例:どこに行けばいいか分からない人が、とりあえず総合受付に回される。
VirtualHostは「1台のApacheで複数サイトを提供するための仕組み」で、考え方は2種類ある。
- IPベース:IPアドレスを分けてサイトを分ける
- 名前ベース:同じIPでも、ホスト名でサイトを分ける(一般的)
外部公開のサーバーでは、IPを増やさずに運用できる 名前ベースVirtualHost が基本になる。
ここが一番の落とし穴。HTTPとHTTPSでは、サイトを選べるタイミングが違う。
HTTPでは「Hostヘッダ」を見てからサイトを選べる。
HTTPSは、HTTPが流れる前にTLSが始まる。だから「Hostヘッダを見る前に証明書を返す必要がある」。ここでSNIが必要になる。
HTTPSでは、証明書を返すタイミングがHTTPより早い。
そのため、1IP:443で複数の証明書を使い分けたい場合、TLS開始時点で「どのホスト名に接続したいか」を伝える必要がある。これがSNI。
ここで「SNIなし」は、古いクライアントだけでなく、検証ツールの指定漏れでも普通に起こる。
だから確認コマンドは基本的に -servername を付けて行う。
HTTPSでApacheが行う判断は、ざっくりこう。
- SNIでホスト名が分かるか
- 分かるなら、そのホスト名に一致するVirtualHostを探す
- 見つからなければ default server(先頭)を使う
ここは「構成の形」を示す参考。既存環境に適用するときは、まず httpd -S で影響範囲を確認する。
# site-a
<VirtualHost *:443>
ServerName site-a.example.com
SSLEngine on
SSLCertificateFile /path/to/site-a/fullchain.crt
SSLCertificateKeyFile /path/to/site-a/privkey.key
DocumentRoot /var/www/site-a
</VirtualHost>
# site-b
<VirtualHost *:443>
ServerName site-b.example.com
SSLEngine on
SSLCertificateFile /path/to/site-b/fullchain.crt
SSLCertificateKeyFile /path/to/site-b/privkey.key
DocumentRoot /var/www/site-b
</VirtualHost>
- ServerName は必須(SNIで到達する先になる)
- 証明書はサイト単位で指定する(同一証明書でまとめる場合を除く)
- 公開CAの証明書は「サーバ証明書+中間証明書」が揃う形(fullchain)が扱いやすい
- 設定の読み込み順で default server が変わるため、意図しない default を作らない
同じIP:443に複数VirtualHostがあるとき、ホスト名一致が取れなければ default server が使われる。
この default は、設定ファイルの読み込み順や定義順で決まりやすい。
- 用件が伝わらない場合、総合受付に回される
その総合受付がどこになるかは「建物の案内(設定順)」で決まる
- 症状:site-aにアクセスしているのにsite-bの証明書が返る
- 典型原因:SNIなし接続(古い端末、検証ツールの指定漏れ)
- 典型原因:default VirtualHost が意図しないものになっている(読み込み順)
- 典型原因:ServerName/ServerAliasの定義ミス(一致しない)
- 症状:HTTP(80)は正しいが、HTTPS(443)は別サイト
- 典型原因:443側のVirtualHostのServerNameが不足
- 典型原因:443のdefaultが想定外
- 症状:PCはOK、スマホや一部ツールで警告
- 典型原因:中間証明書(チェーン)が欠けている
- 典型原因:TLS方式が古い端末に合っていない
- 症状:SNIなしだと必ずその証明書が返る
- 典型原因:default:443 が default 扱いで居座っている
- 対策:既存を壊さない前提なら、まず httpd -S で影響を固定し、confの読み込み順とdefaultを把握する
httpd -S
見るポイント
- default server がどのVirtualHostか
- 443の namevhost が意図どおり並んでいるか
- 想定外の default:443 が default になっていないか
openssl s_client -connect site-a.example.com:443 -servername site-a.example.com |
openssl x509 -noout -subject -issuer -dates
openssl s_client -connect site-a.example.com:443 |
openssl x509 -noout -subject -issuer -dates
SNIあり/なしで返る証明書が変わるなら、default VirtualHost とSNIの挙動を疑う。
- 443の各VirtualHostに ServerName を必ず書く
- 設定追加後は httpd -S で default server を確認する
- 証明書更新後は Apache を再読み込みする(読み直させる)
- 「違う証明書が返る」は、まず SNI付きopenssl で事実確認する
- 共通のssl.confを肥大化させず、サイト別設定はconf.d配下に分離する(影響範囲を狭くする)