2026年9月7日
2026年9月7日
WordPressをHAProxyで負荷分散してサイトの可用性を高める設定方法
HAProxyを前段に置き、WordPressのWebサーバーを2台以上に分けると、1台が落ちたりメンテナンスで止めたりしてもサイトを表示し続けられます。ただし、WordPressは「1台のサーバーで動く」前提で作られている部分が多く、HAProxyの設定だけでは可用性は上がりません。この記事では、負荷分散の前にそろえるべきWordPress側の条件、HAProxyの基本設定とヘルスチェック、HTTPSとクライアントIPの扱い、無停止での入れ替え手順までを説明します。
負荷分散の前に、WordPress側でそろえるもの
複数台のWebサーバーで同じサイトを動かすには、どのサーバーにリクエストが届いても同じ結果になる必要があります。次の5点を先に解決しておきます。
- データベースを共有する:DBを各Webサーバーに置かず、別サーバーやマネージドDBに分ける。DBが1台のままだと、そこが単一障害点として残る点は理解しておく
- アップロードファイルを共有する:
wp-content/uploadsは、管理画面から画像を上げたサーバーにしか保存されない。NFSなどの共有ストレージに置くか、オフロード系プラグインでオブジェクトストレージに保存する - コードを同じにする:WordPress本体・テーマ・プラグインは全台で同じバージョンにする。管理画面からのプラグイン更新は1台にしか反映されないので、デプロイの仕組みで配る(
DISALLOW_FILE_MODSで管理画面からの変更を止める方法もある) - wp-config.php の認証用キーとソルトを全台で同じにする:ログインCookieはこのキーで検証されるため、台ごとに違うとサーバーが切り替わるたびにログアウトされる
- WP-Cronを1か所で動かす:全台の
wp-config.phpでDISABLE_WP_CRONをtrueにし、1台(または別サーバー)のcronからwp cron event run --due-nowを定期実行する
オブジェクトキャッシュを使う場合も、各台のローカルではなく共有のRedisに向けます。台ごとに別のキャッシュを持つと、更新した内容が一部のサーバーでだけ古く見える原因になります。
構成のイメージ
ここでは次の構成を例にします。
- HAProxy:1台(443番でHTTPSを受け、TLSを終端する)
- Webサーバー:web1(10.0.0.11)、web2(10.0.0.12)。Nginx+PHP-FPMまたはApache
- DB・共有ストレージ・Redis:別サーバー
HAProxy自体も1台では単一障害点です。この点への対処は後半で触れます。
HAProxyの基本設定
HAProxyの設定ファイル(多くのディストリビューションでは /etc/haproxy/haproxy.cfg)を、元のファイルをバックアップしてから編集します。以下はHAProxy 2.2以降の書式での例です。http-check send などバージョンによって書式が異なる指定があるため、使っているバージョンのドキュメントと照らし合わせてください(2026年10月現在)。
global
log /dev/log local0
maxconn 4096
stats socket /run/haproxy/admin.sock mode 660 level admin
defaults
mode http
log global
option httplog
option forwardfor
timeout connect 5s
timeout client 30s
timeout server 60s
frontend fe_web
bind :80
bind :443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1
http-request redirect scheme https code 301 unless { ssl_fc }
http-request set-header X-Forwarded-Proto https if { ssl_fc }
acl is_admin path_beg /wp-admin /wp-login.php
use_backend be_admin if is_admin
default_backend be_web
backend be_web
balance roundrobin
option httpchk
http-check send meth GET uri /healthz.php ver HTTP/1.1 hdr Host example.com
http-check expect status 200
server web1 10.0.0.11:80 check inter 5s fall 3 rise 2
server web2 10.0.0.12:80 check inter 5s fall 3 rise 2
backend be_admin
option httpchk
http-check send meth GET uri /healthz.php ver HTTP/1.1 hdr Host example.com
http-check expect status 200
server web1 10.0.0.11:80 check inter 5s fall 3 rise 2
server web2 10.0.0.12:80 check inter 5s fall 3 rise 2 backup
それぞれの意味は次のとおりです。
option forwardforは、元のクライアントIPをX-Forwarded-Forヘッダーに入れてWebサーバーへ渡しますbind :443 ssl crtで指定する証明書ファイルは、証明書・中間証明書・秘密鍵を1つにまとめたPEMです。Let's Encryptを使う場合は、更新後にこのファイルを作り直してHAProxyを再読み込みする処理まで自動化しておきますbe_adminは管理画面とログインを分けたバックエンドで、通常はweb1だけが処理し、web1が落ちたときだけweb2(backup)に切り替わります。管理画面からのファイル書き込みが一部の台に偏ることや、プラグインがPHPセッションに頼っている場合の問題を減らすための工夫です。共有ストレージとデプロイが整っていれば、分けずにまとめてもかまいませんfall 3 rise 2は、3回続けて失敗したら切り離し、2回続けて成功したら戻す、という意味です。interとの組み合わせで、障害の検知にかかる時間が決まります
設定を書いたら、構文チェックを通してから反映する手順にしておきます。チェックで警告やエラーが出た状態で再読み込みすると、設定が反映されないか、HAProxyが起動しなくなります。
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl reload haproxy
reload であれば、処理中の接続を切らずに新しい設定へ移行します。
ヘルスチェック用のエンドポイントを用意する
トップページをヘルスチェックに使うと、WordPress全体を読み込むためチェックのたびに負荷がかかり、ページキャッシュが効いていると「PHPが落ちていてもキャッシュが200を返す」こともあります。WordPressを読み込まない軽いPHPファイルを各Webサーバーに置き、PHPの処理系が動いているかを確認するのが扱いやすい方法です。
<?php
// /var/www/html/healthz.php(各 Web サーバーに配置)
// WordPress は読み込まず、PHP-FPM が応答できるかだけを返す
header( 'Cache-Control: no-store' );
http_response_code( 200 );
echo 'ok';
DBへの接続確認まで含めたくなりますが、DBが落ちたときに全台が同時に切り離され、HAProxyが「全サーバー停止」の状態になるだけで、利用者から見た結果は変わりません。まずはWebサーバー単体の生死を見る用途に絞り、DBの監視は別の仕組みで行うほうが原因の切り分けもしやすくなります。このファイルはHAProxyのIPアドレスからだけアクセスできるよう、Webサーバー側で制限しておくとよいでしょう。
HTTPSとクライアントIPをWordPressに正しく伝える
HAProxyでTLSを終端すると、WebサーバーにはHTTPで届くため、WordPressは「HTTPでアクセスされた」と判断します。この状態だと、管理画面へのアクセスでリダイレクトがループしたり、画像やCSSのURLが http:// になって混在コンテンツの警告が出たりします。wp-config.php の require_once ABSPATH . 'wp-settings.php'; より前に、次を追加します。
// HAProxy が付けた X-Forwarded-Proto を見て HTTPS と判定する
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
このヘッダーは誰でも付けられるので、WebサーバーがHAProxyからの接続しか受け付けない(ファイアウォールでHAProxyのIPだけを許可する)ことが前提です。
クライアントIPについても同様に、WordPressやセキュリティ系プラグインが見る REMOTE_ADDR はHAProxyのIPになります。ログイン試行の制限やコメントのスパム判定が「全員が同じIP」として動いてしまうため、Webサーバー側で元のIPを復元します。Nginxなら real_ip モジュールで set_real_ip_from(HAProxyのIP)と real_ip_header X-Forwarded-For; を、Apacheなら mod_remoteip を使います。信頼する送信元をHAProxyのIPに限定するのがポイントで、すべての送信元を信頼すると、ヘッダーを偽装したIPがそのまま記録されてしまいます。
無停止でサーバーを入れ替える
HAProxyのランタイムAPIを使うと、設定ファイルを書き換えずに特定のサーバーへの振り分けを止められます。global に書いた stats socket を使います。
# web1 を「新規接続を受けない」状態にする(処理中の接続は続行)
echo "set server be_web/web1 state drain" | sudo socat stdio /run/haproxy/admin.sock
# 作業後に戻す
echo "set server be_web/web1 state ready" | sudo socat stdio /run/haproxy/admin.sock
デプロイやOS更新の際は、1台ずつ drain → 作業 → 動作確認 → ready の順で進めれば、利用者からは止まらずに見えます。be_admin にも同じ名前のサーバーがある場合は、そちらも同様に操作します。状態の確認には、管理用のstatsページ(127.0.0.1などに限定して公開)や show servers state コマンドが使えます。
HAProxy自体の冗長化
HAProxyが1台のままでは、そこが止まるとサイト全体が止まります。代表的な対処は次の2つです。
- keepalivedで2台構成にする:HAProxyを2台用意し、VRRPで仮想IPを持ち回る。稼働中の1台が止まると、もう1台が仮想IPを引き継ぐ。クラウドでは仮想IPの扱いに制約があることが多いので、提供元の手順を確認する
- クラウドのマネージドロードバランサーを使う:自分でHAProxyを冗長化する代わりに、クラウド事業者のロードバランサーを前段に置く。HAProxyの細かな制御が不要なら、こちらのほうが運用は軽くなる
どちらを選ぶ場合も、実際に1台を止めて切り替わることを、ステージング環境で試しておいてください。可用性の設定は、障害が起きるまで動作を確認する機会がないためです。
まとめ
- 負荷分散の前に、DB・アップロード・コード・認証キー・WP-Cronを複数台向けにそろえる
- HAProxyは
option httpchkと軽いヘルスチェック用ファイルで、Webサーバーの生死を判定する - 設定は
haproxy -cで構文確認してからreloadで反映する - TLS終端時は
X-Forwarded-ProtoでHTTPSを伝え、クライアントIPはWebサーバー側で信頼する送信元を限定して復元する - ランタイムAPIの drain で1台ずつ入れ替えれば、無停止でデプロイできる
- HAProxy自体の冗長化(keepalivedやマネージドLB)まで含めて設計する