Tuning PHP-FPM: how to calculate pm.max_children

Semih5 min read

Set PHP-FPM's pm.max_children by measuring, not guessing. Measuring process memory, the formula, pm modes, the status page and the slowlog.

There’s one line in PHP-FPM’s config that decides a site’s fate, and on most servers it’s left at its default: pm.max_children. On Debian and Ubuntu it ships as 5. That means at most 5 PHP requests are handled at once, and the sixth visitor waits in line. Fine for a small blog. For an online shop on campaign day, it’s a traffic jam.

The opposite is a problem too: set it to 200 thinking “more is better”, and when traffic arrives the PHP processes eat all the memory and Linux kills your database. The right value comes from measuring, not guessing. Here’s how.

Short answer: pm.max_children ≈ the memory you can give PHP ÷ the average memory of one PHP-FPM process. Round the result down a little and watch it on the status page.

First, how a PHP request is handled

When Nginx receives a .php request, it passes it to PHP-FPM. PHP-FPM keeps a pool of worker processes (children); each worker handles one request at a time. If all workers are busy, the new request waits in a queue. pm.max_children is the largest that pool can grow.

So this setting really answers one question: how many PHP requests at once can my memory carry?

1. How much memory does one process use?

While the site is under normal traffic, measure the average with this command (swap the version number for your own PHP version):

ps --no-headers -o rss -C php-fpm8.3 | awk '{s+=$1; n++} END {printf "%.0f MB\n", s/n/1024}'

This varies a lot with what the site does. A lean PHP site might show 30 to 40 MB; a plugin-heavy WordPress, WHMCS or a large Laravel application can reach 80 to 150 MB. Trust your own measurement, not tables on the internet.

A small note: rss counts memory the processes share (OPcache, for example) once per process. So the measurement comes out a little higher than real usage. That’s a good kind of error: it keeps the calculation on the safe side.

2. How much memory is left for PHP?

Subtract everything else from the server’s total memory:

  • Operating system and Nginx: 300 to 500 MB
  • MySQL or MariaDB: the innodb_buffer_pool_size value plus some headroom
  • Other services such as Redis, Elasticsearch or cron jobs

3. The calculation

Take an example 4 GB server:

Item Memory
Total 4096 MB
Operating system and Nginx −500 MB
MySQL −1200 MB
Left for PHP 2396 MB
Average PHP process 60 MB
pm.max_children 2396 ÷ 60 ≈ 39

From there I’d pick a round number a little lower, like 35. The headroom absorbs the occasional memory-hungry report page or large file upload.

4. Which pm mode?

On Debian and Ubuntu, the config lives at /etc/php/8.3/fpm/pool.d/www.conf. There are three modes:

  • dynamic: the default. A number of workers wait ready and multiply up to pm.max_children as traffic grows. The right choice for most sites.
  • static: always runs pm.max_children workers. Fastest response on busy servers dedicated to one site, but it holds the memory permanently.
  • ondemand: no worker starts until a request arrives. Saves memory when many low-traffic sites share one server, at the cost of a little delay on the first request.

An example dynamic setup:

pm = dynamic
pm.max_children = 35
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 500

pm.max_requests matters too: each worker retires after 500 requests and is replaced by a fresh one. That way a small memory leak in some plugin can’t grow for days and eat the server.

After a change, test the config and reload the service:

sudo php-fpm8.3 -t
sudo systemctl reload php8.3-fpm

5. Watch it: the status page

The calculation is a starting point; the real answer shows up under traffic. Turn on PHP-FPM’s own status page:

pm.status_path = /fpm-status

In Nginx, open that address only to the server itself, not the outside world:

location = /fpm-status {
    allow 127.0.0.1;
    deny all;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

In the output of curl http://127.0.0.1/fpm-status, look at three lines:

  • max children reached: above zero means the pool filled up at least once and requests waited.
  • listen queue: requests waiting in line right now. If it’s regularly above zero, you don’t have enough workers.
  • active processes: workers busy at this moment. If it never comes near the limit, your value is higher than it needs to be.

If the pool fills up and memory is also short, raising pm.max_children isn’t the answer. Then you either make the processes lighter (is OPcache on? any plugins you don’t need?) or you grow the server.

6. Catch slow requests: the slowlog

Sometimes the problem isn’t the number of workers but a few slow pages that keep them busy. PHP-FPM can record the call stack of any request that runs past a time limit:

request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm-slow.log

The log shows, with line numbers, which function the request was stuck in. Most of the time you’ll find either an unindexed query or a call to an external service that doesn’t answer. Always put a timeout on external calls, so your worker isn’t held hostage for 60 seconds when the other side goes quiet.

Summary

  1. Measure the average process memory with ps.
  2. Subtract the other services, divide what’s left by the process size.
  3. Round down a little and add pm.max_requests.
  4. Watch max children reached and listen queue on the status page.
  5. Catch slow requests with the slowlog.

How to tell that a slow server’s problem is PHP-FPM is covered in 10 commands to run before you panic. To keep the PHP side safe, see the PHP security checklist.

More from the rulebook

  1. 5 min read

    The server is slow: 10 Linux commands to run before you panic

    Ten commands that tell you within five minutes whether a slow Linux server is short on CPU, memory, disk or network, or whether the application is to blame.

  2. 6 min read

    The first 30 minutes on a new Linux server: a security checklist

    What to do in the first half hour on a fresh Ubuntu or Debian server. SSH keys, firewall, fail2ban, automatic updates, swap and a first backup.

Chance

Got a project in mind? Let’s open the box: tell me what you want to build and I’ll prepare a proposal with the scope and a roadmap.

Request a quote

or write directly: email address