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.
fastcgi_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
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.
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.
; /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] = offThe 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.
# 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