2026年6月17日
2026年6月17日
VPSでPHP-FPMを最適化する方法|pm.max_childrenの計算・ステータス画面・slowlogで根拠を持って調整する
VPSでのPHP-FPMの最適化は、「1プロセスあたりのメモリを実測し、使えるメモリから pm.max_children を逆算する」ことから始めます。そのうえでステータス画面とslowlogで、足りないのがプロセス数なのか、遅いPHPコードなのかを見分けます。この記事では、WordPressを動かす一般的なVPSを例に、設定値の決め方と効果の確かめ方を説明します。
設定ファイルの場所と反映方法
PHP-FPMの設定は、本体の php-fpm.conf と、プールごとの設定ファイルに分かれています。チューニングで触るのは主にプール設定です。
- Debian / Ubuntu:
/etc/php/8.3/fpm/pool.d/www.conf(8.3の部分はインストールされているPHPのバージョン) - AlmaLinux / Rocky Linux などRHEL系:
/etc/php-fpm.d/www.conf
Ubuntu 24.04の標準パッケージはPHP 8.3です(2026年10月現在)。複数バージョンを入れている場合は、Webサーバーがどのバージョンのソケットに接続しているかを先に確認しておかないと、関係のないプールを調整してしまいます。
変更後は、構文チェックをしてから再読み込みします。
sudo php-fpm8.3 -t
sudo systemctl reload php8.3-fpm
reload は処理中のリクエストを終えてからワーカーを入れ替えるため、通常は restart より安全です。RHEL系ではコマンド名が php-fpm -t、サービス名が php-fpm になります。
プロセス管理モード(pm)を選ぶ
pm は子プロセス(ワーカー)の増減のさせ方を決める設定で、3種類あります。
static:常にpm.max_children個のワーカーを起動しておきます。応答は安定しますが、アクセスが少ない時間もメモリを占有しますdynamic:待機中のワーカー数をpm.min_spare_servers〜pm.max_spare_serversに保ちながら、最大pm.max_childrenまで増減します。多くのディストリビューションの既定値ですondemand:リクエストが来たときにワーカーを起動し、pm.process_idle_timeout(既定10秒)アイドルが続くと終了させます。メモリは節約できますが、アクセスが来た瞬間に起動の待ちが入ります
WordPress専用のVPSでメモリに余裕があるなら dynamic か static、1台に多数の小さなサイトを載せて、それぞれのアクセスがまばらなら ondemand が向いています。
pm.max_children をメモリから計算する
最も重要な値が pm.max_children、つまり同時に処理できるPHPリクエストの上限です。小さすぎるとアクセスが集中したときに待ち行列ができ、大きすぎるとメモリが尽きてスワップやOOM Killerによる強制終了が起こります。
まず、実際に動いているワーカーの平均メモリ使用量を測ります。サイトに普段どおりのアクセスがある時間帯に実行してください。
ps --no-headers -o rss -C php-fpm8.3 | awk '{sum+=$1; n++} END {printf "%d processes, avg %.1f MB\n", n, sum/n/1024}'
RSSにはOPcacheなどの共有メモリが各プロセス分重複して数えられるため、実際の消費より大きめに出ます。安全側の見積もりとしてはこの値で問題ありません。より正確に知りたい場合は smem コマンドでPSS(共有部分を按分した値)を確認します。
次に、PHP-FPMに割り当てられるメモリを決めます。メモリ4GBのVPSでMySQLとNginxが同居する例で考えます。
- OS・Nginx・その他:約0.5GB
- MySQL(InnoDBバッファプールなど):約1GB
- 余裕(ファイルキャッシュ、急な増加への備え):約0.5GB
- PHP-FPMに使える分:約2GB(2,048MB)
平均が80MBなら、2,048 ÷ 80 ≒ 25.6 なので、pm.max_children = 25 が上限の目安です。WordPressで重いプラグインを使っていたり、管理画面で画像処理をしたりすると1プロセスが150MB以上になることもあるため、計算は自分の環境の実測値で行ってください。
; /etc/php/8.3/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 25
pm.start_servers = 6
pm.min_spare_servers = 4
pm.max_spare_servers = 10
pm.max_requests = 500
dynamic の補助値は、待機ワーカーを少し持っておく程度で十分です。pm.start_servers は pm.min_spare_servers と pm.max_spare_servers の間の値でなければならず、範囲外だと起動時にエラーになります。
pm.max_requests は、1つのワーカーがこの回数のリクエストを処理したら自動で作り直す設定です。既定は0(作り直さない)ですが、プラグインのメモリリークでワーカーが徐々に太っていくのを抑えるため、数百程度にしておくのが一般的です。
エラーログで上限到達を見つける
pm.max_children が足りないと、PHP-FPMのエラーログ(Debian/Ubuntuでは既定で /var/log/php8.3-fpm.log)に次のような警告が出ます。
WARNING: [pool www] server reached pm.max_children setting (25), consider raising it
WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 8 children, there are 0 idle, and 12 total children
1つ目は同時処理の上限に達したという意味です。ただし、これを見てすぐに値を上げるのは早計です。上限に達した原因が「アクセスが本当に多い」のか「1リクエストが遅くてワーカーが詰まっている」のかで対処が変わるからです。後者の場合、上限を上げても詰まるワーカーが増えるだけで、メモリが先に尽きます。この見分けにステータス画面とslowlogを使います。
2つ目は、待機ワーカーが足りず急いで増やしているという意味で、pm.min_spare_servers を少し上げれば落ち着くことが多い警告です。
ステータス画面で混雑を数値で見る
プール設定でステータスページを有効にします。
pm.status_path = /fpm-status
ping.path = /fpm-ping
Nginxでは、外部から見えないようにアクセス元を限定して公開します。
location ~ ^/(fpm-status|fpm-ping)$ {
allow 127.0.0.1;
deny all;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
サーバー上から curl http://127.0.0.1/fpm-status を実行すると、次のような項目が確認できます(server_name の設定によっては -H 'Host: example.com' を付けます)。
listen queue:今、空きワーカーを待っているリクエスト数。0が正常ですmax listen queue:起動以降の待ち行列の最大値max children reached:pm.max_childrenに達した回数active processes/idle processes:処理中と待機中のワーカー数slow requests:slowlogの基準時間を超えたリクエスト数
max children reached が増え続けていて slow requests が少なければ、純粋に同時処理数が足りていないので、メモリの計算の範囲内で pm.max_children を上げます。slow requests も増えているなら、先に遅い処理を特定します。?full を付けると、ワーカーごとの処理中URIと経過時間も表示されます。
slowlogで遅いPHPコードを特定する
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm.slow.log
5秒を超えたリクエストについて、その時点で実行中だった関数のスタックトレースがslowlogに記録されます。WordPressであれば、トレースに出てくるファイルのパスから、どのプラグインやテーマのどの処理(外部APIへの通信、重いクエリなど)で止まっているかがわかります。
あわせて、処理が止まったワーカーを強制終了する request_terminate_timeout も設定しておくと、1つの暴走リクエストがワーカーを占有し続けるのを防げます。この値はPHPの max_execution_time とは別に働き、Nginx側の fastcgi_read_timeout より短いと、Nginxが502エラーを返す原因になります。3つの値の関係を揃えてから設定してください。
OPcacheと接続方式も見直す
プロセス数と並んで効果が大きいのがOPcacheです。PHPファイルをコンパイルした結果を共有メモリに保持するため、WordPressのように多数のファイルを読み込むアプリでは、CPU使用率と応答時間の両方が下がります。opcache.memory_consumption と opcache.max_accelerated_files が小さすぎるとキャッシュが溢れて効果が落ちるため、opcache_get_status() などでヒット率と空き容量を確認します。
WebサーバーとPHP-FPMが同じVPS上にあるなら、TCP(127.0.0.1:9000)よりUnixソケット(/run/php/php8.3-fpm.sock)のほうがオーバーヘッドが小さくなります。ソケットを使う場合は listen.owner、listen.group、listen.mode で、Nginxの実行ユーザーが読み書きできるようにしておきます。
調整後に確認すること
設定を変えたら、次の順で効果を確かめます。
free -hとvmstat 5で、ピーク時にスワップが使われていないか- ステータス画面で
listen queueが0に近く、max children reachedが増えなくなったか - エラーログに
server reached pm.max_childrenが出なくなったか - 実際のページの応答時間(TTFB)が改善したか
一度に複数の値を変えると、どの変更が効いたのかわからなくなります。pm.max_children → slowlogで見つけた遅い処理 → OPcache、のように1つずつ変えて記録を残すのが、結果的に早道です。本番環境の設定ファイルは、変更前にコピーを取っておきます。
まとめ
pm.max_childrenは「PHPに使えるメモリ ÷ 1プロセスの実測メモリ」で上限を決める- メモリに余裕のある専用VPSは
dynamicかstatic、小さなサイトが多数ならondemandを選ぶ server reached pm.max_childrenの警告が出ても、すぐに値を上げず、ステータス画面とslowlogで「処理数不足」か「遅いコード」かを見分けるrequest_terminate_timeout、max_execution_time、Nginxのfastcgi_read_timeoutの関係を揃える- OPcacheとUnixソケットも合わせて見直し、変更は1つずつ行って効果を測る