PHP-FPM and the Web Server

nginx takes the request and PHP-FPM runs the code: the socket between them, the pool size that caps concurrency, and a public/ root.

Syntaxfastcgi_pass unix:/run/php/php8.5-fpm.sock;

nginx is a web server. It reads a request off the network and writes bytes back. It cannot run PHP. PHP-FPM, the FastCGI Process Manager, is a separate long-running program that keeps a pool of PHP worker processes ready. nginx forwards the request to it over a Unix socket or a TCP port, an idle worker runs your script, and the worker returns the headers and body for nginx to send on.

The two blocks that matter

try_files $uri /index.php$is_args$args; tells nginx to send the file itself if it exists on disk, and otherwise hand the URL to your front controller. The PHP location block sets fastcgi_pass to the FPM socket and SCRIPT_FILENAME to the file FPM must execute. include fastcgi_params; is shorthand for a long list of individual fastcgi_param lines; the two forms are the same thing, and real configs use both.

The pool decides concurrency

One worker serves one request at a time, so pm.max_children is the hard ceiling on simultaneous PHP requests. With pm = dynamic, FPM starts pm.start_servers workers and keeps the idle count between pm.min_spare_servers and pm.max_spare_servers. Size max_children from memory: usable RAM divided by what one worker actually uses.

Why the root is public/

The document root points at public/, the one directory whose files are meant to be fetched. Your .env, vendor/ and source sit one level above it, where no URL can reach them.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Put a plain PHP application behind nginx on a fresh server without exposing the project directory.
  • Work out how many concurrent requests one box can serve before adding another.
  • Diagnose a 502 after a PHP upgrade changed the socket path from php8.4-fpm.sock to php8.5-fpm.sock.

More examples

A complete nginx server block

The whole contract in one file. try_files inside the PHP block returns 404 for a .php path that does not exist, instead of asking FPM to run something odd. $realpath_root resolves the current symlink, so PHP is given the real release directory rather than the link; that matters on deploy day. fastcgi_read_timeout is nginx's patience: exceed it and the visitor gets 504 even though PHP is still working.

Example · nginx
server {
    listen 80;
    server_name shop.example.com;

    root /srv/shop/current/public;
    index index.php;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ \.php$ {
        try_files $fastcgi_script_name =404;
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.5-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        fastcgi_read_timeout 30s;
    }

    location ~ /\. {
        deny all;
    }
}

The FPM pool file

Twenty workers at roughly 190MB each is the budget this pool asks for. pm.max_requests retires a worker after 500 requests, which hides a slow memory leak in third-party code. php_admin_value and php_admin_flag are per-pool php.ini settings that a script cannot undo with ini_set(); plain php_value and php_flag set a default the script may change. Use pm = ondemand instead on a box with many small, mostly idle sites.

Example · ini
; /etc/php/8.5/fpm/pool.d/shop.conf
[shop]
user = www-data
group = www-data

listen = /run/php/php8.5-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500

php_admin_value[memory_limit] = 192M
php_admin_flag[display_errors] = off

The status endpoint, kept private

Reach it with curl from the machine itself to see active processes, idle processes and the listen queue. A listen queue above zero under normal load means requests are waiting for a worker, which is the signal to raise pm.max_children or to make the slow pages faster. Two failures look similar from outside and are not: 502 means nginx could not reach the socket at all, 504 means PHP took longer than fastcgi_read_timeout.

Example · nginx
# in the pool file:  pm.status_path = /fpm-status

location = /fpm-status {
    allow 127.0.0.1;
    deny all;

    include fastcgi_params;
    fastcgi_pass unix:/run/php/php8.5-fpm.sock;
}

Discussion

  • Be the first to comment on this lesson.