2026年5月24日
2026年5月24日
KeepAlive と接続プールの調整(ブラウザ〜Nginx〜PHP-FPM〜MySQL の区間ごとに考える)
KeepAlive と接続プールの調整は、「どの区間の接続を、どれだけの時間・数だけ使い回すか」を区間ごとに決める作業です。WordPress の一般的な構成では、ブラウザとWebサーバー、リバースプロキシとバックエンド、Webサーバーと PHP-FPM、PHP と MySQL の4つの区間があり、それぞれ設定項目も、増やしすぎたときの副作用も異なります。この記事では、区間ごとに何を調整するのか、現在の状態をどう測るのか、値を決めるときの考え方と、よくある失敗を説明します。
なぜ区間ごとに考えるのか
接続を使い回すと、そのたびに行うTCPの接続確立やTLSのハンドシェイクが減り、応答が速くなり、サーバーの負荷も下がります。その一方で、使い回すために開いたままにしておく接続は、メモリやプロセス、データベースの接続枠を占有します。
つまり KeepAlive の調整は「待ち時間の短縮」と「資源の占有」のバランスを取ることです。どちらに振るべきかは区間ごとに違います。
- ブラウザ〜Webサーバー:利用者の数だけ接続があり、相手の都合で放置されることもある
- リバースプロキシ〜バックエンド:相手は自分たちのサーバーなので、使い回すほど得をしやすい
- Webサーバー〜PHP-FPM:「接続」よりも PHP のワーカー数(プールの大きさ)が本当の制約になる
- PHP〜MySQL:WordPress はリクエストごとに接続し、終われば切るのが基本
以下、この順に見ていきます。値の例はあくまで出発点で、実際のアクセス状況を測ってから調整してください。設定を変える前には設定ファイルのバックアップを取り、可能なら検証環境で負荷をかけて確かめます。
区間1:ブラウザとWebサーバーの KeepAlive
Nginx の場合
Nginx はイベント駆動で動くため、待機中の KeepAlive 接続1本あたりの負担は小さく、極端に短くする必要はあまりありません。主な設定は次の2つです。
http {
keepalive_timeout 30s; # 何もしない接続を保持しておく時間
keepalive_requests 1000; # 1本の接続で処理するリクエスト数の上限
}
keepalive_timeout を長くすると、同じ利用者が続けてページを見たときに接続を使い回せますが、何もしない接続が増えます。Nginx の既定値は75秒です。HTTP/2 の接続の待機時間も、比較的新しい Nginx では keepalive_timeout で制御されます(以前は http2_idle_timeout という別の設定でした)。
待機中の接続数は、stub_status を有効にしていれば「Waiting」の値で確認できます。
# 管理用:ローカルからだけ見られるようにする
location = /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
curl -s http://127.0.0.1/nginx_status
「Waiting」が worker_connections に近づいているなら、タイムアウトを短くするか、worker_connections を見直します。
Apache の場合
Apache では KeepAlive、KeepAliveTimeout、MaxKeepAliveRequests で制御します。注意したいのは MPM(処理方式)との組み合わせです。prefork MPM では、KeepAlive で待機している接続1本が、プロセス1つをそのまま占有します。KeepAliveTimeout を長くすると、何もしない接続でプロセスが埋まり、新しいアクセスを受け付けられなくなります。
KeepAlive On
KeepAliveTimeout 5
MaxKeepAliveRequests 100
Apache の KeepAliveTimeout の既定値は5秒で、prefork を使っているならこの程度の短さを保つのが無難です。event MPM では待機中の接続を別に管理するため、prefork ほど影響は大きくありません。PHP を PHP-FPM で動かしているなら、event MPM への切り替えも検討の対象になります。
区間2:リバースプロキシとバックエンドの接続プール
Nginx をリバースプロキシとして使い、裏のアプリケーションサーバーや Apache に転送している場合、Nginx は何も設定しないとリクエストのたびにバックエンドへの接続を作り、終わると切ることがあります。アクセスが多いと、接続の作成と終了が積み重なり、TIME_WAIT 状態のソケットが大量にたまる原因になります。
upstream ブロックの keepalive で、バックエンドへの接続を使い回せます。
upstream backend {
server 127.0.0.1:8080;
keepalive 32; # ワーカープロセスごとに保持する待機接続の数
keepalive_timeout 60s; # 待機接続を保持する時間
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # HTTP/1.1 で接続を使い回す
proxy_set_header Connection ""; # Connection: close を送らないようにする
proxy_set_header Host $host;
}
}
keepalive 32 は「同時接続の上限」ではなく、「ワーカープロセスごとに、使い終わった接続を最大いくつ取っておくか」という意味です。上限を決めたい場合は、server 行の max_conns など別の設定を使います。proxy_http_version と Connection ヘッダーの2行を忘れると、keepalive を書いても接続が使い回されないことがあるので、セットで入れておきます。各設定の既定値はバージョンによって見直されることがあるため(2026年10月現在)、使っている Nginx の公式ドキュメントで確認してください。
タイムアウトの大小関係に注意する
ロードバランサーと Nginx、Nginx とバックエンドのように接続を使い回す区間では、「受け側のタイムアウトを、送り側より長くする」のが原則です。送り側がまだ使えると思っている接続を、受け側が先に切ってしまうと、そのタイミングで送ったリクエストが失敗し、502 エラーが散発します。
たとえば、前段のロードバランサーのアイドルタイムアウトが60秒なら、その後ろの Nginx の keepalive_timeout は60秒より長くします。同じように、Nginx の upstream の keepalive_timeout は、バックエンド側(Apache の KeepAliveTimeout など)より短くしておきます。クラウドのロードバランサーの既定のタイムアウトはサービスごとに異なるので、利用中のサービスのドキュメントで値を確認してください。
区間3:Webサーバーと PHP-FPM のプール
Nginx と PHP-FPM の間は、Unix ソケットやローカルの TCP で接続します。ここでも fastcgi_keep_conn on; と upstream の keepalive で接続を使い回せますが、同じサーバー内の Unix ソケットであれば、接続の作成コストはもともと小さく、効果が見えにくいことが多いです。
この区間で本当に効くのは、PHP-FPM の「プール」、つまり同時に PHP を処理できるワーカーの数です。
; /etc/php/8.3/fpm/pool.d/www.conf の例(パスは PHP のバージョンで変わる)
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
pm.max_children が少なすぎると、ワーカーが埋まった時点で後続のリクエストは待たされ、遅延やタイムアウトになります。多すぎると、メモリが足りなくなってスワップが発生し、サーバー全体が遅くなります。目安は「PHP に使えるメモリ ÷ PHP-FPM のワーカー1つあたりの平均メモリ」です。
# PHP-FPM ワーカーの平均メモリ(MB)を見る。プロセス名は環境に合わせて変える
ps --no-headers -o rss -C php-fpm8.3 | awk '{s+=$1; n++} END {if (n) printf "%.1f MB x %d\n", s/n/1024, n}'
# 上限に達したかどうかは PHP-FPM のログで確認できる
sudo grep -i 'max_children' /var/log/php8.3-fpm.log
ログに「server reached pm.max_children setting」という警告が頻繁に出ている場合は、ワーカーが足りていません。ただし、遅いプラグインや外部APIの待ちでワーカーが長く埋まっていることも多いので、数を増やす前に、遅いリクエストがないかを request_slowlog_timeout と slowlog で確認すると、根本の原因が見つかることがあります。pm.max_requests は、長く動き続けたワーカーを定期的に入れ替えて、メモリが増え続けるのを防ぐ設定です。
区間4:PHP と MySQL の接続
WordPress は、ページを表示するリクエストごとにデータベースへ接続し、処理が終われば切断するのが基本の動作です。PHP の永続的接続を WordPress 本体で使うための設定は用意されておらず、一般的なサイトでは「接続プール」を作るより、同時接続数の上限を正しく見積もる方が大事です。
考え方は単純で、データベースへの同時接続は、おおむね PHP-FPM のワーカー数の合計を超えません。Webサーバーが複数台あるなら、全台の pm.max_children の合計に、管理作業やバックアップ用の余裕を足した値が、MySQL の max_connections の目安になります。
-- 現在の上限と、これまでの最大同時接続数
SHOW VARIABLES LIKE 'max_connections';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
Max_used_connections が max_connections に達したことがあるなら、上限が低すぎるか、PHP-FPM のワーカー数が多すぎるかのどちらかです。上限に達すると、WordPress では「データベース接続確立エラー」が表示されます。max_connections を大きくすると、接続ごとに使うメモリも増えるので、データベースサーバーのメモリと合わせて判断してください。
Webサーバーの台数が多い構成や、接続の作成コストが大きいマネージドデータベースでは、ProxySQL や Amazon RDS Proxy のような接続プールの仕組みを間に置く方法もあります。ただし、トランザクションやセッション変数の扱いに制約が出ることがあるため、導入する場合は検証環境で WordPress とプラグインの動作を確かめてからにします。
調整したあとの確認
設定を変えたら、変更前と同じ条件で次の値を比べます。
# 状態ごとの TCP 接続数(ESTAB や TIME-WAIT の増減を見る)
ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c
# 応答時間の目安(接続確立と最初の1バイトまでの時間)
curl -o /dev/null -s -w 'connect=%{time_connect} ttfb=%{time_starttransfer}\n' https://example.com/
あわせて、Nginx のエラーログに 502 や upstream のタイムアウトが増えていないか、PHP-FPM のログに max_children の警告が出ていないか、MySQL の Max_used_connections が上限に近づいていないかを確認します。数値を一度に複数変えると、どれが効いたのか分からなくなるので、1つずつ変えて様子を見ると、効果と副作用を切り分けやすくなります。
まとめ
- KeepAlive と接続プールは、ブラウザ〜Webサーバー、プロキシ〜バックエンド、Webサーバー〜PHP-FPM、PHP〜MySQL の区間ごとに考えます
- Apache の prefork では KeepAlive の待機がプロセスを占有するので、タイムアウトは短めにします
- Nginx の upstream で接続を使い回すには、
keepaliveとproxy_http_version 1.1、Connection ""をセットで設定します - 接続を使い回す区間では、受け側のタイムアウトを送り側より長くして、502 の散発を防ぎます
- PHP-FPM の
pm.max_childrenはメモリから見積もり、MySQL のmax_connectionsはワーカー数の合計に余裕を足して決めます