2026年5月26日
2026年5月26日
Apacheでバーチャルホストを設定する方法|複数のWordPressをサイトごとに分けて動かす構成
1台のサーバーで複数のWordPressを動かすなら、Apacheのバーチャルホストを「サイトごとに公開ディレクトリ・ログ・PHPの実行ユーザーを分ける」形で組むと、トラブルの切り分けも被害の封じ込めもしやすくなります。この記事では、名前ベースのバーチャルホストを複数サイト運用向けに設計し、apachectl -S や curl で振り分けを確かめながら本番に入れるまでの流れを説明します。
バーチャルホストの基本的な書き方(1サイト分の設定とHTTPS化)は別の記事でも扱っているため、ここでは「2サイト目以降を足すときの設計」と「意図しないサイトが表示されるときの見分け方」に重点を置きます。
Apacheはどのバーチャルホストを使うかをどう決めているか
設定を書く前に、振り分けの仕組みを押さえておくと後のトラブルがかなり減ります。Apacheはリクエストを受けると、次の順で担当のバーチャルホストを選びます。
- 接続を受けた IPアドレスとポート(
*:80や*:443)が一致する<VirtualHost>を候補にする - 候補の中から、リクエストの Host ヘッダーと
ServerNameまたはServerAliasが一致するものを選ぶ - どれにも一致しなければ、その IP:ポートで最初に読み込まれたバーチャルホストを使う
3番目が重要です。DNSだけ向けて設定を書き忘れたドメインや、IPアドレス直打ちのアクセスは、読み込み順で先頭にあるサイトに流れます。「別のサイトが表示される」という相談の多くはこれが原因です。先頭には、どのドメインにも属さない受け皿用のバーチャルホストを置いておくと安全です。
なお、HTTPSでは暗号化の前に証明書を選ぶ必要があるため、ブラウザが送る SNI(接続先のホスト名)を手がかりに同じ仕組みで振り分けます。現在の主要なブラウザやクライアントは SNI に対応しているので、1つのIPアドレスで複数の証明書を使い分けられます。
サイトごとに分けるものを決める
複数サイトを同居させるときは、少なくとも次の3つをサイト単位で分けておくことをおすすめします。
- 公開ディレクトリ:
/var/www/site1.example.com/publicのように、ドメイン名でディレクトリを切ります。publicの外に、Git管理外の設定やバックアップの一時置き場を作れるようにしておくと便利です。 - ログ: アクセスログとエラーログをサイトごとに出します。1つのログに全サイトが混ざると、どのサイトで500エラーが出たのかを追うだけで時間がかかります。
- PHPの実行ユーザー: PHP-FPM のプールをサイトごとに作り、それぞれ別の Linux ユーザーで動かします。1つのサイトが改ざんされても、他サイトのファイルを書き換えにくくなります。
3つ目は必須ではありませんが、mod_php で全サイトが Apache と同じユーザーで動いている構成では、1サイトの脆弱性がサーバー上の全WordPressに波及しやすくなります。サイト数が増えるほど分離の効果は大きくなります。
設定例:サイトごとのPHP-FPMプールとバーチャルホスト
ここでは Ubuntu(Debian系)を例にします。RHEL系(AlmaLinux・Rocky Linuxなど)では設定ファイルの置き場所が /etc/httpd/conf.d/ になり、a2ensite のような補助コマンドがない点が主な違いです。作業前に /etc/apache2/ と /etc/php/ を丸ごとバックアップし、可能ならステージング環境で先に試してください。
まず、サイト用のユーザーとディレクトリを作ります。
sudo useradd --system --home /var/www/site1.example.com --shell /usr/sbin/nologin site1
sudo mkdir -p /var/www/site1.example.com/public /var/www/site1.example.com/logs
sudo chown -R site1:site1 /var/www/site1.example.com/public
次に PHP-FPM のプールを追加します。パスに含まれる PHP のバージョン(例では 8.3)は、サーバーに入っているものに読み替えてください。
; /etc/php/8.3/fpm/pool.d/site1.conf
[site1]
user = site1
group = site1
listen = /run/php/php8.3-fpm-site1.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
php_admin_value[open_basedir] = /var/www/site1.example.com/public:/tmp
listen.owner と listen.group は、ソケットに接続する側(Apache の実行ユーザー)に合わせます。ここが合っていないと、Apache 側で 503 エラーになります。open_basedir は PHP から読めるディレクトリを制限する設定で、他サイトのディレクトリを覗かれにくくする補助的な対策です。プラグインによっては一時ディレクトリなどへのアクセスで警告が出ることがあるので、導入後にエラーログを確認してください。
続いてバーチャルホストを書きます。
# /etc/apache2/sites-available/site1.example.com.conf
<VirtualHost *:80>
ServerName site1.example.com
ServerAlias www.site1.example.com
DocumentRoot /var/www/site1.example.com/public
<Directory /var/www/site1.example.com/public>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/php8.3-fpm-site1.sock|fcgi://localhost"
</FilesMatch>
ErrorLog ${APACHE_LOG_DIR}/site1.example.com-error.log
CustomLog ${APACHE_LOG_DIR}/site1.example.com-access.log combined
</VirtualHost>
AllowOverride All は、WordPress がパーマリンク用に書き出す .htaccess を有効にするための指定です。.htaccess を使わず設定をすべてバーチャルホスト側に書く運用なら AllowOverride None にでき、そのほうがリクエストごとのファイル探索が減ります。ただし、WordPress やプラグインが .htaccess に書き込む前提の機能が動かなくなるため、切り替えるときは書き込まれていた内容をバーチャルホストに移してください。
最後にモジュールとサイトを有効化し、構文チェックをしてから再読み込みします。
sudo a2enmod proxy_fcgi rewrite
sudo a2ensite site1.example.com.conf
sudo apache2ctl configtest
sudo systemctl reload php8.3-fpm
sudo systemctl reload apache2
configtest が Syntax OK を返さないまま再読み込みすると、設定によっては Apache が起動できなくなり全サイトが止まります。複数サイトを載せたサーバーでは、この確認を省かないことが大切です。
2サイト目以降は、ユーザー名・ディレクトリ・ソケット名・ログ名を置き換えて同じ手順を繰り返します。ソケット名の付け間違いで別サイトのプールに接続してしまうと、PHP が別ユーザーで動いて書き込み権限のエラーになるので、命名規則を決めておくと間違いにくくなります。
振り分けが意図どおりかを確かめる
設定が読み込まれたら、まず apache2ctl -S(RHEL系では apachectl -S または httpd -S)で Apache がどう解釈しているかを見ます。
sudo apache2ctl -S
出力には、IP:ポートごとに default server と、続いて各 ServerName と設定ファイルの行番号が並びます。ここで確認するのは次の2点です。
default serverが受け皿用のバーチャルホストになっているか(実サイトのどれかになっていないか)- 追加したサイトが、想定したポート(80 と 443)の両方に出ているか
次に、DNSを切り替える前にサーバー上で curl を使って Host ヘッダーを指定し、正しいサイトが返るかを確かめます。
# HTTP: Host ヘッダーを指定してローカルに問い合わせる
curl -sI -H "Host: site1.example.com" http://127.0.0.1/
# HTTPS: 名前解決だけをサーバーのIPに向ける(SNIも正しく送られる)
curl -sI --resolve site1.example.com:443:127.0.0.1 https://site1.example.com/
HTTPS の確認では -H "Host: ..." だけだと SNI が送られず、受け皿の証明書が返ってしまうことがあります。--resolve を使うと、ホスト名はそのままで接続先だけを変えられます。手元のPCから確かめたい場合は、hosts ファイルにサーバーのIPアドレスとドメインを一時的に書く方法もあります。確認が終わったら hosts の記述を消し忘れないようにしてください。
HTTPSを足すときの注意
HTTPS 化は、各サイトの *:80 設定ができてから Certbot などで証明書を取得し、*:443 のバーチャルホストを追加する順番が分かりやすいです。Certbot の Apache プラグインを使うと 443 側の設定を自動生成してくれますが、生成された設定にも先ほどの FilesMatch(PHP-FPM のソケット指定)が引き継がれているかを確認してください。80 側にしか書いていないと、HTTPS でアクセスしたときだけ PHP が別の方法で処理される、あるいはソースがそのまま表示される、といった事態になります。
共通の設定を何度も書くのが手間なら、Include で共通部分を別ファイルにまとめる方法があります。ただし、サイトごとに異なる値(ソケット名やログ名)まで共通化しないよう注意が必要です。
意図しないサイトが表示されるときの確認順
症状ごとに、可能性の高い順に確認するポイントをまとめます。
- 受け皿のサイトが表示される:
ServerNameの綴り、wwwありなしのServerAlias漏れ、a2ensiteのし忘れを確認します。apache2ctl -Sに該当ドメインが出ていなければ、設定が読み込まれていません。 - HTTPSだけ別のサイトや証明書警告になる: 443 側のバーチャルホストがない、または
ServerNameが違います。openssl s_client -connect サーバーIP:443 -servername site1.example.comで返ってくる証明書のドメインを確認すると切り分けられます。 - 503 Service Unavailable になる: PHP-FPM のプールが起動していないか、ソケットのパスや権限が合っていません。
systemctl status php8.3-fpmと、エラーログのAH01079などのプロキシ関連のメッセージを確認します。 - パーマリンクが404になる:
AllowOverrideがNoneのまま、または mod_rewrite が無効です。 - メディアのアップロードやプラグイン更新が失敗する:
public配下の所有者がプールのユーザーと一致しているかを確認します。
どの症状でも、まずそのサイト専用のエラーログを見るのが近道です。ログをサイトごとに分けておく利点がここで効いてきます。
まとめ
- Apache は IP:ポート、次に
ServerName/ServerAliasの順でバーチャルホストを選び、一致しなければ先頭のものを使う。先頭には受け皿を置く。 - 複数のWordPressを同居させるなら、公開ディレクトリ・ログ・PHP-FPM の実行ユーザーをサイトごとに分けると、障害の切り分けと被害の封じ込めがしやすい。
- 設定変更のたびに
configtestを通し、apache2ctl -Sとcurl --resolveで振り分けを確かめてから DNS を切り替える。 - HTTPS 側の設定にも PHP-FPM のソケット指定が入っているかを確認する。
- コマンドやパスは Ubuntu の例です(2026年10月現在)。ディストリビューションや PHP のバージョンに合わせて読み替え、作業前にはバックアップを取ってください。