2026年8月27日

2026年8月27日

HAProxy のトラブル事例と対処法(503・504・リダイレクトループ・証明書)

HAProxy で WordPress を負荷分散・SSL終端していると、503、504、リダイレクトループ、証明書エラー、ログイン状態が保てない、といったトラブルに出会います。この記事では、まず HAProxy のログと状態表示から症状を読み分ける方法を説明し、そのうえでよくある事例を起きやすい順に、確認コマンドと直し方をセットで紹介します。

HAProxy の設定構文やログ書式はバージョンによって差があります。この記事は 2.x〜3.x 系を想定しています(2026年10月現在)。設定を変える前にはバックアップを取り、使っているバージョンのドキュメントで確認してから、可能ならステージングで試してください。

まず症状を読み分ける

ログの終了ステータスを見る

HAProxy の HTTP ログ(option httplog)には、接続がどう終わったかを示す2文字の終了ステータスが記録されます。1文字目が「誰が・何で」終わらせたか、2文字目が「どの段階で」終わったかを表します。代表的なものは次のとおりです。

  • --:正常終了
  • sH:サーバー側のタイムアウト。バックエンドが応答ヘッダーを返す前に timeout server に達した
  • SC:バックエンドへの接続が拒否された、またはエラーになった
  • CD / cD:クライアント側の切断、またはクライアント側のタイムアウト
  • PR:HAProxy 自身がリクエストを拒否した(ACL など)

ログの出力先はディストリビューションによって異なり、rsyslog 経由でファイルに書く構成や journald に出る構成があります。設定の log 行と、/var/log/haproxy.log または journalctl -u haproxy を確認してください。

# 直近のエラーを確認(ログファイル運用の場合)
sudo tail -n 200 /var/log/haproxy.log | grep -vE ' -- '

状態表示でバックエンドを確認する

Runtime API(stats ソケット)を有効にしておくと、各サーバーが UP か DOWN か、直近のヘルスチェック結果は何かをすぐに確認できます。

global
    stats socket /run/haproxy/admin.sock mode 660 level admin
echo "show stat" | sudo socat stdio /run/haproxy/admin.sock | cut -d, -f1,2,18,37
echo "show info" | sudo socat stdio /run/haproxy/admin.sock | grep -E 'CurrConns|Maxconn'

show stat の出力は CSV で、列の位置はバージョンによって増えることがあります。status や check_status の列を見出し行で確かめてから読んでください。stats ソケットを level admin で開くとサーバーの切り離しなども操作できるため、パーミッションは管理者だけに絞ります。

事例1:503「No server is available」

最も多いのが、ブラウザに 503 と「No server is available to handle this request.」が表示されるケースです。これは HAProxy が振り分け先の全サーバーを DOWN と判断している状態で、WordPress 本体の問題ではなくヘルスチェックの失敗であることがほとんどです。

確認する順番は次のとおりです。

  1. show stat でサーバーが DOWN になっているか、check_status が何か(L4CON なら接続不可、L7STS なら HTTP ステータス不一致)を見る
  2. HAProxy のサーバーから、ヘルスチェックと同じリクエストを curl で送ってみる
  3. バックエンドの Web サーバーや PHP-FPM、データベースが動いているかを確認する

よくある原因は、ヘルスチェックのパスがリダイレクトやエラーを返していることです。WordPress のトップページはデータベースに接続できないと 500 番台を返すため、DB 障害がそのまま「全台 DOWN」に見えます。また、Host ヘッダーなしのリクエストに対してバックエンドが既定のバーチャルホストを返し、それが 404 になっている例もあります。

HAProxy 2.2 以降では http-check send で Host ヘッダーを指定できます。

backend wp_servers
    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

ヘルスチェック専用の軽いファイルを置くと、WordPress 全体を読み込まずに済み、チェックの負荷も下がります。DB の死活まで含めるかどうかは、「DB が落ちたら全台を外したいか」で決めます。

事例2:504 Gateway Timeout

管理画面での一括更新、インポート、重いレポート表示などで 504 が出る場合は、ログの終了ステータスが sH になっていないかを確認します。sH なら、バックエンドの処理が timeout server より長くかかっています。

対処は二段階です。まず、その処理が本当に時間のかかるものかを PHP のスロークエリログや slowlog(PHP-FPM)で確認します。プラグインの不具合で無限に待っているなら、タイムアウトを延ばしても解決しません。正当な処理であれば、管理画面へのリクエストだけタイムアウトを延ばす方法があります。

backend wp_admin
    timeout server 300s
    server web1 10.0.0.11:80 check

フロントエンドで path_beg /wp-admin の ACL を使ってこのバックエンドに振り分けます。全体のタイムアウトを大きくすると、詰まったリクエストが接続を占有し続け、別の障害を招くことがあります。PHP 側の max_execution_time や Web サーバーのタイムアウトも、HAProxy より短いと先に切れるため、経路全体でそろえます。

事例3:HTTPS 化した後のリダイレクトループ

HAProxy で TLS を終端すると、WordPress には HTTP でリクエストが届きます。WordPress が「HTTP で来た」と判断して HTTPS にリダイレクトし、HAProxy がまた HTTP で渡す、というループが起こります。

HAProxy 側で転送元のプロトコルを伝えます。

frontend fe_https
    bind :443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1
    option forwardfor
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    default_backend wp_servers

WordPress 側では wp-config.php で X-Forwarded-Proto が https のときに $_SERVER['HTTPS'] = 'on'; を設定します。バックエンドが HAProxy からしか到達できないことが前提です。さらに上流に CDN がある場合は、CDN とオリジンの SSL 方式の組み合わせでもループが起こるので、curl -I で Location ヘッダーを一段ずつ追って、どこが戻しているかを特定します。

事例4:証明書の更新後もエラーになる

HAProxy の crt には、サーバー証明書、中間証明書、秘密鍵を1つにまとめた PEM ファイルを指定するのが一般的な方法です。certbot で更新しても、この結合ファイルは自動では作り直されないため、「更新したのに古い証明書が出る」状態になりがちです。

certbot のデプロイフックで結合とリロードを行います。

#!/bin/sh
# /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh
set -e
DOMAIN=example.com
cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem \
    /etc/letsencrypt/live/$DOMAIN/privkey.pem \
    > /etc/haproxy/certs/$DOMAIN.pem
chmod 600 /etc/haproxy/certs/$DOMAIN.pem
haproxy -c -f /etc/haproxy/haproxy.cfg && systemctl reload haproxy

中間証明書が抜けていると、PC のブラウザでは表示できても一部のスマートフォンや API クライアントで検証エラーになります。openssl s_client -connect example.com:443 -servername example.com -showcerts で、チェーンが送られているかを確認してください。certbot renew --dry-run で更新処理が通るかも定期的に試しておくと安心です。

事例5:ログインが切れる・カートが消える

複数台に振り分けると、ログインしたはずがすぐ切れる、画像をアップロードしたのに表示されない、といった相談が増えます。HAProxy のスティッキーセッションで抑え込む前に、原因を確認します。

  • 各サーバーの wp-config.php で認証キーとソルト(AUTH_KEY など)が一致しているか。違うと、別のサーバーに振られた時点でログイン Cookie が無効になる
  • wp-content/uploads が共有ストレージやオブジェクトストレージで共有されているか
  • PHP セッションやローカルファイルにデータを置くプラグインを使っていないか

WordPress のログイン状態は Cookie とデータベースで管理されるため、設定がそろっていればスティッキーセッションは必須ではありません。どうしても必要な場合は、cookie SERVERID insert indirect nocache と各 server 行の cookie web1 で Cookie によるサーバー固定ができます。ただし固定するとサーバーごとの負荷が偏りやすくなります。

事例6:設定変更後に起動しない・接続が詰まる

設定変更後に HAProxy が起動しない場合は、リロード前に構文を検証していないことが原因の大半です。haproxy -c -f /etc/haproxy/haproxy.cfg を毎回実行してから systemctl reload haproxy します。

アクセスが増えたときだけ遅くなる場合は、show info の接続数が maxconn に張り付いていないか、show stat のキュー(qcur)が伸びていないかを見ます。上限に達すると、リクエストは待たされた末にタイムアウトします。maxconn を上げる前に、バックエンドの PHP-FPM のワーカー数が受け止められる量かを確認してください。

まとめ

  • まずログの終了ステータス(sH、SC など)と show stat で、どこで止まっているかを確認する
  • 503 はヘルスチェックの失敗が大半。Host ヘッダーと期待ステータスを見直す
  • 504 は処理時間とタイムアウトの関係。必要な経路だけ延ばし、経路全体でそろえる
  • リダイレクトループは X-Forwarded-Proto と WordPress 側の HTTPS 判定で解消する
  • 証明書は結合 PEM の作り直しとリロードを certbot のデプロイフックで自動化する
  • ログイン切れは認証キーとアップロードの共有を先に疑い、スティッキーセッションは最後の手段にする

お気軽にご相談ください

お見積りへ お問い合わせへ
▲