2026年5月24日
2026年5月24日
SNIで複数ドメインの証明書を1台のサーバーで切り替える設定(Nginx・Apache)
1台のサーバー・1つの IP アドレスで複数のドメインを HTTPS 運用するときは、TLS の拡張機能である SNI(Server Name Indication)を使い、接続してきたホスト名ごとに別の証明書を返します。この記事では、SNI の仕組みを押さえたうえで、Nginx と Apache でドメインごとに証明書を切り替える設定、DNS を切り替える前に正しい証明書が返るかを確かめる方法、そして「別のドメインの証明書が出てしまう」ときの原因の調べ方を説明します。
SNI が必要な理由
HTTP の世界では、1つの IP アドレスに複数のサイトを載せても、リクエストの Host ヘッダーを見ればどのサイト宛てかが分かります。ところが HTTPS では、Host ヘッダーが届くのは TLS の暗号化が始まった後です。サーバーは暗号化を始める時点で証明書を提示する必要があるため、そのままでは「どのドメインの証明書を出せばいいか」が分かりません。
SNI はこの問題を解決するための拡張で、クライアントが TLS 接続の最初のメッセージ(ClientHello)に接続先のホスト名を入れて送ります。サーバーはその名前を見て、対応する証明書を選びます。現在の主要なブラウザ、curl、PHP などのクライアントは SNI に対応しているため、一般的な Web サイトでは「SNI 前提で設定する」と考えて問題ありません。
なお、ClientHello の SNI は通常は暗号化されずに送られるため、通信経路から接続先のホスト名が見えます。これを隠す ECH(Encrypted Client Hello)という仕組みの普及も進んでいますが、対応状況はブラウザや CDN によって異なります(2026年10月現在)。サーバー側で証明書を切り替える設定そのものは、ECH の有無に関係なく必要です。
SNI・SAN・ワイルドカードの使い分け
複数ドメインを HTTPS 化する方法は SNI だけではありません。設計の段階で次の選択肢を比べておくと、後の運用が楽になります。
- ドメインごとに別の証明書(SNI で切り替え):ドメインの追加・削除や更新を個別に行える。運営者や更新タイミングが違うサイトを同居させる場合に向く
- 1枚に複数の名前を入れた証明書(SAN):証明書は1枚で済むが、名前を追加するたびに再発行が必要。証明書の中身を見れば同居しているドメインの一覧が分かってしまう
- ワイルドカード証明書:
*.example.comのように同じドメインのサブドメインをまとめて扱える。別のドメインには使えず、取得には DNS 認証が必要になることが多い
この記事の主題は1つ目の「ドメインごとに別の証明書を返す」構成です。実際には「サイトごとに SAN 証明書(example.com と www.example.com を1枚に)を取り、それを SNI で切り替える」という組み合わせが最もよく使われます。
証明書を用意する
Let's Encrypt を使う場合は、サイトごとに証明書を分けて取得します。--cert-name を付けておくと、保存先のディレクトリ名を自分で決められて管理しやすくなります。
sudo certbot certonly --nginx --cert-name site-a -d a.example.com -d www.a.example.com
sudo certbot certonly --nginx --cert-name site-b -d b.example.net -d www.b.example.net
# 取得済みの証明書と対象ドメインの一覧
sudo certbot certificates
保存先は /etc/letsencrypt/live/site-a/fullchain.pem のようになります。Apache の場合は --nginx を --apache に、Web サーバーのプラグインを使わない場合は --webroot などに読み替えてください。有料の証明書を使う場合も、サーバー証明書と中間証明書を連結したファイルと秘密鍵をドメインごとに用意する点は同じです。
Nginx の設定
Nginx では、ドメインごとに server ブロックを作り、それぞれに server_name と証明書を書くだけで SNI による切り替えになります。
# どの server_name にも一致しない接続を受け止める既定のサーバー
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_reject_handshake on; # 証明書を出さずにハンドシェイクを拒否
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name a.example.com www.a.example.com;
ssl_certificate /etc/letsencrypt/live/site-a/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/site-a/privkey.pem;
root /var/www/site-a;
# ...PHP-FPM などの設定
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name b.example.net www.b.example.net;
ssl_certificate /etc/letsencrypt/live/site-b/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/site-b/privkey.pem;
root /var/www/site-b;
}
押さえておきたい点は3つあります。
1つ目は既定のサーバーです。SNI を送らないクライアントや、設定に無いホスト名で接続してきたクライアントには、default_server を付けたブロック(無ければその listen を最初に書いたブロック)の証明書が返されます。既定を明示しないと、たまたま先に読み込まれたサイトの証明書が無関係な接続に返され、別のドメインが同居していることが見えてしまいます。ssl_reject_handshake on; は既定サーバーで証明書を出さずに接続を拒否する指定で、比較的新しい Nginx で使えます。古いバージョンでは自己署名の証明書を既定サーバーに置く方法が取られてきました。
2つ目は **listen の書き方をそろえること**です。Nginx は先に「IP アドレスとポート」で候補を絞り、その中で server_name を比べます。あるサイトだけ listen 203.0.113.10:443 ssl; のように IP を指定し、別のサイトが listen 443 ssl; のままだと、名前が合っていても想定外のブロックが選ばれることがあります。
3つ目は HTTP/2 の書き方です。新しい Nginx では listen に http2 を付ける書き方は非推奨になり、http2 on; を使います。使っているバージョンに合わせて書き分けてください(nginx -v で確認できます)。
設定を変えたら、構文チェックをしてから再読み込みします。
sudo nginx -t && sudo systemctl reload nginx
Apache の設定
Apache でも同様に、ドメインごとに VirtualHost を作り、それぞれに証明書を指定します。
<VirtualHost *:443>
ServerName a.example.com
ServerAlias www.a.example.com
DocumentRoot /var/www/site-a
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/site-a/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/site-a/privkey.pem
</VirtualHost>
<VirtualHost *:443>
ServerName b.example.net
ServerAlias www.b.example.net
DocumentRoot /var/www/site-b
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/site-b/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/site-b/privkey.pem
</VirtualHost>
Apache では、同じ *:443 を持つ VirtualHost のうち最初に読み込まれたものが既定になります。設定ファイルは名前順に読み込まれることが多いため、000-default-ssl.conf のような名前で既定用の VirtualHost を先頭に置くと意図をはっきりさせられます。SNI を送らないクライアントを拒否したい場合は SSLStrictSNIVHostCheck on が使えますが、古い監視ツールなどが接続できなくなることがあるため、影響を確認してから有効にしてください。
Apache 2.4.8 以降では、中間証明書を含む連結ファイルを SSLCertificateFile に指定するのが基本で、SSLCertificateChainFile は非推奨です。古い記事の設定をそのまま使うと混乱しやすい部分です。
sudo apachectl configtest && sudo systemctl reload apache2 # RHEL系は httpd
DNS を切り替える前に証明書を確かめる
新しいサーバーにサイトを移す場合、DNS を切り替える前に「その名前で接続すると正しい証明書が返るか」を確認できます。
# IP を直接指定し、SNI として a.example.com を送る
echo | openssl s_client -connect 203.0.113.10:443 -servername a.example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# SNI を送らない場合に何が返るか(既定サーバーの確認)
echo | openssl s_client -connect 203.0.113.10:443 -noservername 2>/dev/null \
| openssl x509 -noout -subject
# curl で名前解決だけ差し替えて HTTP レベルまで確認
curl -sv --resolve a.example.com:443:203.0.113.10 https://a.example.com/ -o /dev/null
-ext と -noservername は OpenSSL 1.1.1 以降のオプションです。subjectAltName に接続したドメイン名が含まれていれば、証明書の選択は成功しています。curl の出力で SSL certificate verify ok が表示され、期待どおりのステータスコードが返ればサイトとしても動いています。
別のドメインの証明書が返るときの原因
ブラウザで「この接続ではプライバシーが保護されません」などの警告が出て、証明書の詳細を見ると別ドメインのものだった、という場合は次の順に確認します。
- **
server_name/ServerNameの綴りや抜け**:www付きを書き忘れているケースが最も多く見られます。nginx -T | grep server_nameやapachectl -Sで、実際に読み込まれている名前の一覧を確認します - 設定ファイルが読み込まれていない:
sites-enabledへのリンク忘れ、a2ensiteの実行忘れ、拡張子の違いで include されていないなど。nginx -Tで全設定を出力すると確かめやすくなります - **
listenの不一致**:前述のとおり、IP 指定の有無やIPv4/IPv6の片側だけの指定で、候補になるブロックが変わります - 再読み込みをしていない:証明書を更新・追加した後に reload していないと、古い設定のまま動き続けます
- 前段で TLS が終端されている:CDN やロードバランサーを使っている場合、利用者に見える証明書は前段のものです。オリジンの設定を直しても変わらないときは、前段の証明書設定を確認してください
また、複数のドメインを1枚の証明書でまかなっている構成で、ブラウザが HTTP/2 の接続を別ドメインに使い回し、サーバーが 421(Misdirected Request)を返すことがあります。ドメインごとに証明書を分ける構成ではこの問題は起きにくくなります。
WordPress で複数サイトを運用する場合
1台のサーバーに独立した WordPress を複数置く場合は、上の設定をサイトごとに増やしていけば対応できます。マルチサイトで独自ドメインを割り当てる場合も、ドメインごとに証明書と server_name を追加する考え方は同じです。HTTPS 化した後は、各サイトの「WordPress アドレス」と「サイトアドレス」が https:// になっているか、混在コンテンツが残っていないかも確認してください。設定を変える前には、Web サーバーの設定ファイルとサイトのバックアップを取っておくと、問題が起きたときにすぐ戻せます。
まとめ
- SNI はクライアントが接続先のホスト名を TLS の最初に送る仕組みで、1つの IP で複数ドメインの証明書を切り替えられる
- ドメインごとの証明書、SAN、ワイルドカードは用途が違う。サイトごとに SAN 証明書を取り、SNI で切り替える構成が扱いやすい
- Nginx は
serverブロックごと、Apache はVirtualHostごとに証明書を指定する。既定のサーバーを明示し、listenの書き方をそろえる openssl s_client -servernameとcurl --resolveで、DNS を切り替える前に正しい証明書が返るかを確かめられる- 別ドメインの証明書が返るときは、名前の抜け、設定の読み込み、listen の不一致、reload 忘れ、前段の TLS 終端の順に確認する