PHP-FPM (FastCGI Process Manager) is the standard process manager for running PHP behind a web server. It keeps pools of PHP worker processes alive and hands them requests over the FastCGI protocol, so web servers such as Nginx, Apache, Caddy and OpenLiteSpeed can serve static files themselves and pass only PHP requests to a separate, independently tuned service.
PHP-FPM has been part of the official PHP distribution since version 5.3.3 and is the default way to run PHP in modern web server stacks. Its pool model gives administrators direct control over concurrency, memory use and isolation between sites.
How PHP-FPM handles requests
PHP-FPM runs as a master process with child worker processes. The master reads the configuration, opens the listening sockets and starts, stops and recycles workers. Each worker handles one request at a time: it receives the script path and request variables from the web server, executes the PHP code and returns the response over the same connection.
The web server and PHP-FPM communicate through a Unix domain socket or a TCP socket. A Unix socket such as /run/php/php8.3-fpm.sock avoids network overhead when both run on the same host, while TCP, typically on port 9000, allows PHP-FPM to run on separate servers or containers.
Workers are long-lived, so expensive initialization happens only once. OPcache keeps compiled PHP bytecode in shared memory across all workers of the master, which removes the cost of parsing scripts on every request and is the single most important performance setting for PHP-FPM.
Pools and process manager modes
A pool is a group of workers with its own socket, user account, limits and PHP settings. Running each site in a separate pool under its own system user isolates sites from each other, so a compromised or overloaded site does not affect its neighbors. Every pool chooses one of three process manager modes.
| Mode | Behavior | Best for | Trade-off |
|---|---|---|---|
| static | Keeps a fixed number of workers running | Busy dedicated servers with steady load | Memory is reserved even when idle |
| dynamic | Scales workers between spare-server limits | General-purpose servers | Needs four settings tuned together |
| ondemand | Starts workers only when requests arrive | Many low-traffic sites on one host | First request after idle is slower |
The settings below control each pool. Their values depend on available memory and the memory used per worker, which ranges from about 30 MB for simple scripts to several hundred MB for heavy applications.
| Setting | Purpose | Typical starting point |
|---|---|---|
| pm.max_children | Maximum concurrent workers | Usable RAM divided by average worker size |
| pm.start_servers | Workers started at launch (dynamic) | About 25 percent of max_children |
| pm.min_spare_servers / pm.max_spare_servers | Idle worker range (dynamic) | Small range around start_servers |
| pm.max_requests | Requests before a worker is recycled | 500-1000 to contain memory leaks |
| request_terminate_timeout | Hard limit per request | Slightly above the web server timeout |
PHP-FPM compared with other ways to run PHP
Before PHP-FPM became standard, PHP usually ran embedded inside Apache as mod_php. Other approaches exist today as well. The table places PHP-FPM among them.
| Approach | Process model | Isolation per site | Typical pairing |
|---|---|---|---|
| PHP-FPM | Separate FastCGI worker pools | Strong, separate users per pool | Nginx, Apache event MPM, Caddy |
| mod_php | PHP inside every Apache process | Weak, one Apache user | Apache prefork MPM |
| LSAPI | LiteSpeed-specific PHP processes | Strong with suEXEC | OpenLiteSpeed and LiteSpeed |
| Application servers | Persistent PHP application in memory | Per application | Modern PHP frameworks with workers |
Compared with mod_php, PHP-FPM lets Apache use the efficient event MPM and keeps static file requests away from heavy PHP processes. Compared with plain CGI, it avoids starting a new process for every request. FastCGI itself is the protocol; PHP-FPM is the implementation that manages PHP workers speaking it.
Tuning PHP-FPM in practice
Tuning starts with measuring real worker memory under production load, not with copying values from another server. Multiply the average worker size by pm.max_children and confirm that the total, plus the database, cache and operating system, fits in RAM without swapping.
The status page (pm.status_path) reports active and idle workers, the listen queue and the counter max children reached. A non-zero listen queue or a growing max children counter means requests are waiting; the slow log (request_slowlog_timeout) records stack traces of requests that exceed a threshold, which identifies the code responsible.
| Observation | Likely meaning | Action |
|---|---|---|
| Max children reached grows | Pool too small for peak load | Raise max_children if memory allows |
| Server swaps under load | Too many workers for RAM | Lower max_children or optimize code |
| Workers slowly grow in memory | Leaks in application or extensions | Lower pm.max_requests |
| Many slow log entries | Slow queries or external calls | Fix the code path, not the pool size |
Common PHP-FPM errors and misconceptions
Most visible PHP-FPM failures appear first as web server errors. A 502 Bad Gateway usually means the socket is missing or permissions prevent the web server from connecting; a 504 Gateway Timeout means a worker took longer than the web server allowed. Both point to PHP-FPM, but the cause differs.
A frequent misconception is that more workers always mean more capacity. Each worker is bound by CPU and by slow backends; when the database is the bottleneck, extra workers only add contention. Another mistake is sharing one pool among unrelated sites, which removes isolation and makes per-site limits impossible.
In simple terms
PHP-FPM is a team of workers waiting to run PHP code. When a visitor opens a page built with PHP, the web server does not run the code itself. It passes the job to one of these waiting workers, gets the finished page back and sends it to the visitor.
The administrator decides how many workers each website gets. Too few, and visitors wait in line; too many, and the server runs out of memory.
A practical example
An agency hosts eight WordPress sites on one server with 8 GB of RAM, using Nginx and PHP-FPM. During a campaign, one shop site becomes slow and returns occasional 504 errors, while the other sites stay fast.
The status page of the shop’s pool shows max children reached increasing every few minutes. Workers use about 90 MB each, and the pool is limited to 10 children. The slow log reveals that a product filter waits on an unindexed database query.
The team adds the missing index and raises the shop pool to 20 workers in dynamic mode, staying within memory because the other sites run in ondemand pools. Response times drop below one second, and the separate pools keep the other sites unaffected throughout the campaign.