If a website page is sometimes fast and suddenly slow, especially when opening the dashboard, logging in, or accessing dynamic pages, the issue may not necessarily be bandwidth or traffic volume. Often, the bottleneck lies in PHP-FPM: the layer that executes PHP code before the server sends HTML to visitors.
PHP-FPM operates with a number of processes or workers. Each worker handles a PHP request. If all workers are busy, new requests enter a queue. Visitors ultimately see long wait times, while website owners may only observe seemingly normal traffic numbers.
Why Can PHP-FPM Become a Bottleneck?
Imagine a shop with five cashiers. These five cashiers represent PHP-FPM workers. When simple orders come in, one cashier can serve many customers quickly. However, if there is one order that takes a long time—such as a heavy database query, an external API call, or a report generation process—that cashier gets held up longer.
Problems arise when several workers experience the same issue. The pm.max_children limit determines the maximum number of PHP requests that can be served simultaneously. If all child processes are in use, the next request is not processed immediately. PHP documentation explains that this parameter acts as a limit on concurrent requests for PHP-FPM.
However, blindly increasing the pm.max_children number is not a solution. Each worker uses memory. If the number is too high, the server may experience RAM pressure, start swapping, or even terminate processes due to running out of memory. The website may appear stronger for a few minutes, only to become increasingly unstable.
Signs That PHP-FPM Workers Are Running Out of Capacity
The symptoms are not always the same, but the following patterns are quite common:
- TTFB increases on pages that require PHP, while static files remain fast.
- The website is fast after clearing the cache but slows down again when dynamic pages are accessed frequently.
- PHP-FPM logs show messages like max children reached.
- CPU is not always at 100 percent, but load average, RAM usage, or the number of PHP processes remains high.
- Specific requests take much longer than others.
- The admin dashboard is slow, while cached public pages still feel normal.
The PHP-FPM status can help confirm these suspicions. One important indicator is whether the maximum number of child processes has ever been reached. The PHP-FPM status page can also display information such as the number of active processes and requests that are classified as slow.
Don't Start by Adding Workers
A safer step is to find out why workers are taking too long. A low number of workers can indeed be a problem, but workers that take a long time to finish are often the root cause.
1. Enable Slowlog Carefully
PHP-FPM provides a slowlog mechanism to log backtrace requests that exceed a certain timeout. Set request_slowlog_timeout to a reasonable value, such as a few seconds, and specify the location of the slowlog.
The goal is not to log all requests but to identify the code that holds workers for too long. After collecting data, check whether the source comes from database queries, large loops, API calls, plugins, or other processes running synchronously.
2. Differentiate Between CPU, Memory, and Wait Time Bottlenecks
Active PHP workers do not always mean the CPU is working hard. A request may simply be waiting for a database or a connection to a third-party service. Therefore, check server metrics simultaneously:
- High CPU: likely indicates heavy computation, large parsing, or too many processes running.
- Low RAM: the number of workers may be too high, or the application is using excessive memory.
- Low CPU but long requests: suspect database, disk, network, or external APIs.
- High Disk I/O: check for excessive logging, file operations, backups, or databases frequently reading data from disk.
Looking at just one graph often leads to incorrect conclusions. A website can have low CPU but still be slow because all workers are waiting for other resources.
3. Identify Requests That Should Not Run on the User Path
Some tasks do not need to be performed while visitors wait for the page to finish loading. Examples include generating large thumbnails, sending emails, calculating reports, synchronizing stock, or fetching data from external APIs.
Move these tasks to cron, queues, or background processes if the application architecture allows. This way, web requests only create tasks and immediately return a response, while long-running jobs are processed outside the user interaction path.
How to Determine the Value of pm.max_children
The correct value depends on the RAM and memory consumption of each worker. Do not copy numbers from other servers. First, measure the memory usage of PHP processes under normal load, then leave space for the operating system, web server, database, and other services.
In simple terms, the approach can be described as follows:
maximum_workers = RAM allocated for PHP-FPM / average RAM per workerThe result of this calculation is not a final number. Leave a margin so that spikes in memory usage do not immediately cause the server to run out of RAM. After changing the configuration, monitor memory usage, the number of requests, response times, and errors during busy periods.
If the server has 4 GB of RAM and the database application runs on the same machine, not all memory should be allocated to PHP-FPM. The database also requires space for buffers and connections. This is why PHP-FPM configuration needs to be viewed as part of the system, not as a separate setting.
Don't Forget to Check the Database and Cache
PHP-FPM often shows symptoms rather than being the source of the problem. A single slow query can hold a worker for several seconds. If that query is called on every request, a queue will form even if traffic is not high.
Check the most frequently called queries and their execution times. Use the slow query log or Performance Schema to group statement patterns and identify queries that consume the most time. After that, examine the execution plan, indexes, the number of rows read, and whether the same data can be served through object cache.
Cache also needs to be used appropriately. Page caching can reduce the PHP load for public pages, while object caching helps store the results of queries or frequently used objects. However, do not cache personal data without a clear separation mechanism. Account pages, shopping carts, and user search results typically require different treatment from public article pages.
What You Can Do Now
- Note when the website is slow and which pages are affected.
- Compare static pages, dynamic public pages, and the admin dashboard.
- Check the PHP-FPM status and look for the max children reached indicator.
- Enable slowlog to find requests that take too long.
- Measure the average memory usage of each worker before increasing
pm.max_children. - Examine database queries and API calls occurring within requests.
- Move long-running tasks to cron or queues if possible.
- Test changes one by one, then compare TTFB, error rates, RAM, CPU, and response times.
What Does This Mean for Us?
A slow website does not always require a more expensive server. Sometimes the issue is a small queue caused by a long process, inefficient queries, or worker configurations that do not match RAM capacity.
Good fixes are not just about increasing the worker numbers. The goal is to make each request complete faster, reduce synchronous work, and ensure that the database and cache assist PHP-FPM—not make it wait longer.
Sources & Further Reading
- PHP Manual: PHP-FPM Configuration
- PHP Manual: PHP-FPM Status Page
- web.dev: Optimize Largest Contentful Paint
- MySQL Reference Manual: Statement Digests
- MDN: Cache-Control Header
– Rio Yotto @rioyotto
