PHP-FPM

JA JASWANT Updated 04 Nov 2026
6 min read ·Lesson 233 of 238

What Is PHP-FPM?

In simple terms: A web server such as Nginx is excellent at handling connections and serving files, but it cannot run PHP itself. PHP-FPM is a separate service that keeps a group of PHP worker processes ready. The web server passes each PHP request to PHP-FPM, a free worker runs the script, and the result goes back to the web server and on to the browser. Technically: PHP-FPM (FastCGI Process Manager) is PHP's production SAPI for serving web requests over the FastCGI protocol. A master process reads the configuration and manages one or more pools of worker processes. Each worker handles one request at a time, so the number of workers determines how many PHP requests can run concurrently. Pools can run as different users with different settings, isolating applications from each other.

Browser ──HTTPS──► Nginx ──FastCGI (Unix socket)──► PHP-FPM master
                    │                                   ├── worker 1 (running a request)
                    └── serves static files directly    ├── worker 2 (idle)
                                                         └── worker 3 (running a request)

Installing and Running PHP-FPM

# Debian / Ubuntu
sudo apt install php8.4-fpm
sudo systemctl enable --now php8.4-fpm
systemctl status php8.4-fpm
CommandEffect
sudo systemctl reload php8.4-fpmGraceful reload: re-reads configuration, replaces workers after their current requests finish, clears OPcache
sudo systemctl restart php8.4-fpmFull restart (brief interruption)
sudo php-fpm8.4 -tTest the configuration for errors before reloading

Configuration files on Debian/Ubuntu:

FileContents
/etc/php/8.4/fpm/php-fpm.confGlobal settings (logging, includes)
/etc/php/8.4/fpm/pool.d/www.confThe default pool
/etc/php/8.4/fpm/php.iniphp.ini used by FPM (separate from the CLI's)

Connecting Nginx to PHP-FPM

server {
    listen 443 ssl;
    server_name shop.example;
    root /var/www/shop/current/public;
    index index.php;
    location / {
        try_files $uri /index.php$is_args$args;
    }
    location = /index.php {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root/index.php;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    }
    location ~ \.php$ {
        return 404;
    }
}
  • Requests for existing files (CSS, images) are served by Nginx directly; everything else goes to the front controller (see Hiding PHP).
  • fastcgi_pass points to the pool's Unix socket (faster than TCP when Nginx and PHP-FPM run on the same machine; use 127.0.0.1:9000 or a container name with TCP).
  • $realpath_root resolves symlinks, so deployments that switch a current symlink pick up the new release reliably.

Configuring a Pool

; /etc/php/8.4/fpm/pool.d/shop.conf
[shop]
user = shop
group = shop
listen = /run/php/shop.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 40
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 1000
request_terminate_timeout = 60s
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/shop-slow.log
pm.status_path = /fpm-status
ping.path = /fpm-ping
php_admin_value[memory_limit] = 256M
php_admin_value[error_log] = /var/log/php-fpm/shop-error.log
php_admin_flag[log_errors] = on

Key Settings

SettingMeaning
user / groupThe account the workers run as — a dedicated user per application isolates sites from each other
listenSocket (or address) the web server connects to
pmProcess manager mode: dynamic, static, or ondemand
pm.max_childrenMaximum number of workers = maximum concurrent PHP requests
pm.start_servers, pm.min_spare_servers, pm.max_spare_serversInitial and idle worker counts (dynamic mode)
pm.max_requestsRecycle a worker after this many requests — limits the impact of memory leaks
request_terminate_timeoutKill requests that run too long
request_slowlog_timeout + slowlogLog a stack trace for requests slower than the threshold — great for finding slow code
pm.status_path, ping.pathMonitoring endpoints (restrict access to them in Nginx)
php_admin_value[...]php.ini settings for this pool that scripts cannot override

Process Manager Modes

ModeBehaviorBest For
dynamicKeeps a range of idle workers, grows up to max_childrenMost servers
staticAlways runs exactly max_children workersDedicated servers with steady, high traffic
ondemandStarts workers only when requests arrive, stops idle onesLow-traffic sites, many small pools on one server

Sizing pm.max_children

Each worker uses memory. If too many run at once, the server starts swapping or the out-of-memory killer terminates processes. A simple estimate:

<?php
function maxChildren(int $totalMemoryMb, int $reservedMb, int $averageWorkerMb): int
{
    return max(1, intdiv($totalMemoryMb - $reservedMb, $averageWorkerMb));
}
// 4 GB server, 1 GB reserved for the OS, MySQL, Redis; workers average 60 MB
echo maxChildren(4096, 1024, 60) . "\n";

Output

51
  1. Measure the average worker memory under real load (for example, ps -o rss= -C php-fpm8.4 and average the values, or monitoring tools).
  2. Subtract memory needed by the operating system and other services.
  3. Divide the rest by the average worker size; round down and leave a safety margin. If the log reports server reached pm.max_children setting, requests are waiting for free workers. Either the limit is too low for the traffic — or, more often, requests are slow (database queries, external APIs) and occupy workers for too long. Fix slow requests first.

Monitoring With the Status Page

With pm.status_path enabled and access restricted to internal addresses, the status page shows the pool's state:

pool:                 shop
process manager:      dynamic
start time:           17/Oct/2026:09:00:00 +0530
start since:          3600
accepted conn:        15234
listen queue:         0
max listen queue:     3
idle processes:       6
active processes:     2
total processes:      8
max active processes: 14
max children reached: 0
slow requests:        1
  • listen queue greater than 0 means requests are waiting for a worker.
  • max children reached counts how often the pool hit its limit.
  • slow requests counts entries written to the slow log.

Finishing the Response Early: fastcgi_finish_request()

PHP-FPM offers a function that sends the response to the browser and then continues running the script:

<?php
echo json_encode(['status' => 'Order received']);
fastcgi_finish_request();   // the user gets the response now
// Continue working without making the user wait
sendConfirmationEmail($orderId);

This is convenient for short follow-up tasks, but the worker remains busy until the script ends. For anything slow or important, use a real queue with background workers.


Troubleshooting

SymptomLikely Cause
502 Bad GatewayPHP-FPM not running, wrong socket path, or socket permissions (listen.owner/listen.mode)
504 Gateway TimeoutScript exceeded the web server's fastcgi_read_timeout (or FPM's terminate timeout)
Slow responses under loadWorkers exhausted (max_children reached), slow queries, external API calls
Changes to php.ini have no effectThe CLI's file was edited, or FPM was not reloaded
Memory growing per workerLeaks in long requests — lower pm.max_requests and investigate

Check the FPM log (/var/log/php8.4-fpm.log), the pool's error and slow logs, and the Nginx error log.


Common Mistakes

Setting pm.max_children Too High

Effect: The server runs out of memory under load. Fix: Calculate from measured worker memory and available RAM.

Running All Sites in One Pool as One User

Effect: A compromised site can read the others' files. Fix: One pool and user per application.

Exposing the Status Page

Effect: Internal information is visible to everyone. Fix: Restrict status and ping paths to internal IPs or authentication.

Not Reloading After Configuration Changes

Effect: Old settings remain active. Fix: Test with php-fpm8.4 -t, then reload.


Best Practices

  • Run PHP behind Nginx (or another server) with PHP-FPM, using Unix sockets on the same host.
  • Create a pool with its own user per application, and enforce limits with php_admin_value.
  • Size pm.max_children from measured memory, and recycle workers with pm.max_requests.
  • Enable the slow log and status page (restricted) to monitor performance.
  • Move slow work to queues instead of tying up workers. Official reference: https://www.php.net/manual/en/install.fpm.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings Function Arguments PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro PHP Class Constants Encryption with Sodium Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions array_map / array_filter / array_reduce Array Functions PHP Superglobals $GLOBALS What Is `$_SERVER` ? $_REQUEST $_POST $_GET $_FILES $_COOKIE PHP Type Juggling MySQL Order By MySQL Delete Data MySQL Update Data MySQL Limit Data MySQL Joins with PHP Pagination with PHP PDO Introduction PDO Connect PDO Prepared Statements PDO Fetch Modes PSR-4 Autoloading Installing Extensions (PECL / PIE) OPcache & Performance PHP-FPM Project: Contact Form with Email Project: Login & Registration System Project: CRUD App with MySQL Project: Shopping Cart with Sessions Project: REST API with PHP
Ask about this post
AI Ask about this post

Ask questions about PHP-FPM and get answers drawn from it.

Signed-in readers only.