
If your site goes slow under a little traffic, then throws 502 or 504 errors, check the PHP-FPM log. On Ubuntu with PHP 8.5 that is /var/log/php8.5-fpm.log; use your own version number if it is different:
sudo grep max_children /var/log/php8.5-fpm.log | tail
If you see this, you have found the bottleneck:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
It means every PHP worker was busy, so new requests waited in a queue until one freed up. Nginx or Caddy waited too, and eventually gave up. The fix is usually to raise the number, but by how much depends on your RAM, and sometimes raising it makes things worse. Here is how to get it right.
Why the limit is 5 in the first place
Ubuntu's PHP-FPM package and the official php:*-fpm Docker images both ship a pool configuration with conservative defaults:
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
Five workers can handle five PHP requests at the same moment. That is safe on the smallest server, and too low for almost any real WordPress or Laravel site. A single page view can trigger several PHP requests at once (admin-ajax, REST calls, cron), and a slow request holds its worker the whole time.
Step 1: measure how much memory one worker uses
Do this while the site is serving normal traffic, not straight after a restart:
ps --no-headers -o rss -C php-fpm8.5 | awk '{s+=$1; n++} END {printf "%d workers, avg %.0f MB\n", n, s/n/1024}'
Typical figures are 40–80 MB per worker for a WordPress site with a handful of plugins, more with WooCommerce or a page builder, and 30–70 MB for a Laravel app. Use the number you measure, not these. If a few workers are far larger than the rest, something on a specific page is using a lot of memory, and that is worth finding before you scale.
In Docker, run the same command inside the container, replacing -C php-fpm8.5 with -C php-fpm, or read the per-container figure from docker stats.
Step 2: work out how much RAM PHP can have
Take the server's total RAM and subtract everything else that needs to live there:
- MySQL or MariaDB: check with
ps -o rss= -C mysqld(ormariadbd). Often 300–600 MB on a small server. - Redis, Nginx or Caddy, queue workers, and the operating system itself: allow 200–400 MB in total.
- Headroom for spikes: leave at least 10–15% of total RAM free.
Then divide what is left by the per-worker figure:
pm.max_children = RAM for PHP ÷ average worker size
Worked examples, assuming 60 MB per worker:
| Server RAM | Database + rest + headroom | Left for PHP | pm.max_children |
|---|---|---|---|
| 1 GB | ~650 MB | ~350 MB | 5–6 |
| 2 GB | ~900 MB | ~1.1 GB | 16–18 |
| 4 GB | ~1.3 GB | ~2.7 GB | 40–45 |
Notice the 1 GB row: the default of 5 is about right there. If you are hitting the limit on a 1 GB server, the real fix is more RAM or less work per request, not a bigger number. Set it too high and, under load, PHP takes memory MySQL needs, the kernel's out-of-memory killer steps in, and your database dies instead of a request queueing. Our swap and out-of-memory guide shows how to tell if that has already happened.
Step 3: change the pool settings
Edit the pool file, for example /etc/php/8.5/fpm/pool.d/www.conf. For a 2 GB server with the figures above:
pm = dynamic
pm.max_children = 16
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
pm.max_requests restarts each worker after 500 requests, which stops slow memory leaks in plugins or extensions from building up over days.
For a low-traffic site where RAM matters more than the first request after a quiet spell, pm = ondemand with pm.process_idle_timeout = 10s starts workers only when needed and stops them when idle. Keep pm.max_children at the same calculated value either way.
Test the configuration, then reload:
sudo php-fpm8.5 -t
sudo systemctl reload php8.5-fpm
In Docker, put the overrides in a file such as zz-pool.conf mounted into /usr/local/etc/php-fpm.d/. Files load alphabetically, so a name starting with zz- overrides the image's www.conf. Then restart the container.
When raising it won't help
Workers sit busy for two reasons: there are a lot of requests, or each request is slow. If it's the second, more workers just means more requests stuck waiting on the same slow thing, using more RAM while they wait. Turn on the slow log to find out which it is:
request_slowlog_timeout = 5s
slowlog = /var/log/php8.5-fpm-slow.log
Every request that takes longer than 5 seconds will now write a stack trace to that file, showing exactly which function it was waiting in. The common answers:
- A slow database query. Missing indexes, or a plugin running an unindexed query on every page. Check MySQL's slow query log.
- An external HTTP call. A plugin licence check, a payment API or a remote font, called during page load with a long timeout.
- Too few database connections. If MySQL's
max_connectionsis lower than the total number of PHP workers across your sites, workers queue for a connection. That fails with its own error, covered in ERROR 1040: Too Many Connections. - WordPress cron on page load. On busy sites, set
define('DISABLE_WP_CRON', true);and runwp cron event run --due-nowfrom system cron every few minutes instead. Our Cron Expression Generator builds the schedule line.
A persistent object cache also takes a lot of load off each request; see Redis object cache for WordPress without running out of memory.
Watching it afterwards
PHP-FPM has a built-in status page. Add pm.status_path = /fpm-status to the pool, then expose that path only to localhost in your web server and check it with curl:
curl -s http://127.0.0.1/fpm-status
Watch active processes and max children reached. If max children reached keeps climbing after your change, and the slow log is quiet, you have genuinely outgrown the server. That is the point to move up a size; our 2027 VPS price comparison shows what the next tier costs at each provider.
FAQ
Is this related to Nginx's "502 Bad Gateway"?
It can be. When every worker is busy for long enough, the web server times out waiting. But a 502 can also mean PHP-FPM isn't running at all, or is listening on a different socket than Nginx expects. See this IPv6 localhost trap for another common cause.
I host several sites. Do I calculate per pool?
Yes. Every pool's pm.max_children adds up against the same RAM. Split the PHP budget between pools according to traffic, so one busy site cannot starve the others.
Does OPcache change the numbers?
OPcache lives in shared memory, so it isn't multiplied per worker, but it does appear in each worker's RSS. Measuring after warm-up, as in step 1, slightly overestimates per-worker size. That errs on the safe side.
The default pool values and directives in this guide match the PHP-FPM configuration shipped with PHP 8.5 on Ubuntu 26.04 and the official PHP Docker images, checked against the PHP manual as of October 2026. The per-worker sizes are typical ranges; always measure your own.
Comments 0
Be the first to comment.
Leave a comment