Ubuntu Setup PHP: Configuring PHP-FPM with Nginx (and Tuning It Right)

Installing PHP on Ubuntu is the easy part. The work that actually determines whether your site is fast, stable, and able to survive a traffic spike happens *after* the install: configuring the PHP-FPM process manager, wiring it into Nginx, and tuning `php.ini` for your real workload.

This guide focuses on exactly that. We assume PHP is already on the box — if it isn’t, start with the base install first () and come back here to turn a bare PHP runtime into a properly configured, production-shaped PHP-FPM + Nginx stack.

Key Takeaways
PHP-FPM runs PHP as a dedicated process pool; Nginx passes `.php` requests to it over a Unix socket via FastCGI.
• The FPM pool config (`www.conf`) controls the user, the listen socket, and the process manager mode (`dynamic`, `static`, or `ondemand`).
• `pm.max_children` is the single most important tuning knob — set it from available RAM, not guesswork.
• Key `php.ini` values to tune: `memory_limit`, `upload_max_filesize`, `post_max_size`, `max_execution_time`, and OPcache.
• Always test with a temporary `phpinfo()` file, then delete it — it leaks server internals.

Why Use PHP-FPM with Nginx Instead of Apache + mod_php?

Nginx has no built-in PHP interpreter. Instead of embedding PHP in the web server process (the old Apache `mod_php` model), the modern stack keeps them separate: Nginx handles connections and static files, and hands dynamic `.php` requests to PHP-FPM (FastCGI Process Manager) over a socket.

That separation is the whole point. Nginx stays lean and event-driven for serving assets and proxying, while PHP-FPM manages a pool of PHP worker processes you can size and tune independently. You get better memory behaviour under concurrency, finer control over how many PHP workers exist, and the ability to run different pools for different sites with different users and limits.

How Do You Install and Verify PHP-FPM?

If you followed the base install guide, you may already have it. To install the FPM package explicitly:

“`bash sudo apt update sudo apt install php-fpm “`

Confirm the service exists and note the exact version, since the version number appears in every path and service name:

“`bash php -v systemctl status php8.3-fpm “`

Adjust `php8.3-fpm` to match whatever version `php -v` reports (for example `php8.2-fpm`). Enable it so it survives reboots:

“`bash sudo systemctl enable –now php8.3-fpm “`

How Do You Configure the PHP-FPM Pool (www.conf)?

Each pool is defined in its own file. The default pool lives at:

“` /etc/php/8.3/fpm/pool.d/www.conf “`

Open it and focus on a handful of directives that matter most.

Pool User and Group

These set the Unix user the PHP workers run as. For most setups this should match the user that owns your web files (often `www-data`):

“`ini user = www-data group = www-data “`

Running each site’s pool under a dedicated user is a strong isolation practice on multi-site servers — a compromise in one site’s PHP can’t trivially read another’s files.

Unix Socket vs TCP Listen

PHP-FPM can listen on a Unix socket or a TCP port. For a single machine where Nginx and PHP-FPM live together, a Unix socket is faster and avoids the TCP stack:

“`ini listen = /run/php/php8.3-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660 “`

Use TCP (`listen = 127.0.0.1:9000`) only when PHP-FPM runs on a separate host from Nginx. The `listen.owner`/`listen.group` lines ensure Nginx is allowed to talk to the socket.

Process Manager Mode: dynamic, static, or ondemand

The `pm` directive decides how PHP-FPM spawns workers:

  • `dynamic` — keeps a baseline of idle workers and scales up to a ceiling. The sensible default for most sites.
  • `static` — a fixed number of workers, always running. Predictable RAM use, best for high, steady traffic.
  • `ondemand` — spawns workers only when requests arrive and kills idle ones. Lowest idle footprint, ideal for low-traffic or memory-constrained boxes.

A typical `dynamic` block:

“`ini pm = dynamic pm.max_children = 20 pm.start_servers = 4 pm.min_spare_servers = 2 pm.max_spare_servers = 8 pm.max_requests = 500 “`

After any change, restart and check syntax:

“`bash sudo php-fpm8.3 -t sudo systemctl restart php8.3-fpm “`

The one knob that matters most: `pm.max_children`. Of every setting in this file, this is the one that makes or breaks your server. It caps the number of PHP worker processes that can run at once — which means it directly caps how much RAM PHP can consume and how many requests you can process in parallel. Set it too high and a traffic spike spawns more workers than your RAM can hold; the box starts swapping or the OOM killer terminates processes, and the site falls over hard. Set it too low and even a modest burst of visitors queues behind a handful of workers, so requests stall and time out while your CPU sits half-idle. The right value isn’t a guess — it’s arithmetic. Measure the real memory footprint of one PHP worker under load (commonly somewhere in the tens of megabytes, depending on your app and extensions), reserve RAM for Nginx, the database, and the OS, then divide the remainder by that per-worker figure:

“` pm.max_children = (RAM available for PHP) / (average memory per PHP worker) “`

Round down, leave headroom, and re-check as your application grows. This single calculation prevents the most common PHP-FPM outage there is.

How Do You Wire Nginx to PHP-FPM?

Nginx needs a `location` block that catches `.php` requests and forwards them to the FPM socket via FastCGI. Inside your server block (for example `/etc/nginx/sites-available/your-site`):

“`nginx server { listen 80; server_name example.com; root /var/www/example.com/public; index index.php index.html;

location / { try_files $uri $uri/ /index.php?$query_string; }

location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }

location ~ /\. { deny all; } } “`

The critical lines:

  • `fastcgi_pass` must point at the *exact same* socket path you set as `listen` in `www.conf`. A mismatch here is the number-one cause of `502 Bad Gateway`.
  • `include snippets/fastcgi-php.conf;` and `include fastcgi_params;` load the standard FastCGI parameter mappings Nginx ships with.
  • The `SCRIPT_FILENAME` parameter tells PHP-FPM which file on disk to execute.

Enable the site and reload:

“`bash sudo ln -s /etc/nginx/sites-available/your-site /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx “`

`nginx -t` validates the config *before* you reload — never skip it.

Which php.ini Settings Should You Tune?

PHP-FPM uses its own `php.ini`, separate from the CLI one:

“` /etc/php/8.3/fpm/php.ini “`

These are the directives that most often need changing for real applications. PHP-FPM must be restarted (not just Nginx reloaded) for `php.ini` changes to take effect.

Setting Typical starting value What it controls
`memory_limit` `256M` Max RAM a single script may use before being killed.
`upload_max_filesize` `64M` Largest single file a user can upload.
`post_max_size` `64M` Max size of an entire POST body; must be ≥ `upload_max_filesize`.
`max_execution_time` `60` Seconds a script may run before timeout.
`opcache.enable` `1` Caches compiled PHP bytecode — major speed win.
`opcache.memory_consumption` `128` MB of RAM OPcache uses for cached bytecode.
`opcache.max_accelerated_files` `10000` Number of PHP files OPcache can hold.
`opcache.validate_timestamps` `1` (dev) / `0` (prod) Whether OPcache re-checks files for changes.

A few rules of thumb:

  • Keep `post_max_size` greater than or equal to `upload_max_filesize`, or large uploads silently fail.
  • OPcache is the cheapest performance gain available — confirm it’s on. In production, setting `opcache.validate_timestamps = 0` skips disk checks for even more speed, but you must clear the cache (restart FPM) on every deploy.
  • If you raise `max_execution_time`, remember Nginx has its own `fastcgi_read_timeout` that can cut a request off first.

Apply changes:

“`bash sudo systemctl restart php8.3-fpm “`

How Do You Test That PHP Is Actually Working?

Create a temporary info file in your web root:

“`bash echo “

Visit `http://example.com/info.php` in a browser. You should see the PHP info page, confirming Nginx is correctly handing `.php` requests to PHP-FPM. Verify the Server API reads `FPM/FastCGI`, the loaded version is correct, and OPcache appears as enabled.

Then delete it immediately:

“`bash sudo rm /var/www/example.com/public/info.php “`

A `phpinfo()` page exposes your full configuration, file paths, loaded modules, and environment — valuable reconnaissance for an attacker. It should never be left reachable.

If you instead hit a 502 Bad Gateway, the usual suspects are: the `fastcgi_pass` socket path not matching `www.conf`, PHP-FPM not running, or socket permissions (`listen.owner`/`listen.group`) blocking Nginx. Check the logs:

“`bash sudo tail -f /var/log/nginx/error.log sudo journalctl -u php8.3-fpm -f “`

Let DarazHost Handle the Tuning for You

Building and tuning a PHP-FPM + Nginx stack by hand is rewarding, but it’s also one more thing to maintain, patch, and watch under load. DarazHost gives you both paths:

  • Managed hosting with PHP-FPM/Nginx (or LiteSpeed) pre-tuned out of the box — sensible `pm.max_children`, OPcache, and `php.ini` defaults already dialled in, so you ship instead of fiddle.
  • VPS with full root access when you want to build and tune your *own* PHP-FPM + Nginx stack exactly as described above — total control, your rules.

Every plan runs on fast SSD storage and is backed by 24/7 support from people who actually know the stack. Whether you want it handled or want the keys, DarazHost has a fit.

Frequently Asked Questions

What’s the difference between PHP-FPM and the `php` CLI binary? The `php` command runs scripts from the terminal and uses the CLI `php.ini`. PHP-FPM is a long-running service that serves PHP to a web server over FastCGI and uses its *own* `php.ini` under the `fpm` directory. Editing the wrong file is a common reason changes “don’t apply.”

Should I use a Unix socket or a TCP port for `listen`? Use a Unix socket when Nginx and PHP-FPM are on the same server — it’s faster and avoids the TCP stack. Use TCP (`127.0.0.1:9000`) only when PHP-FPM runs on a separate host from Nginx.

Why am I getting a 502 Bad Gateway after configuring everything? Almost always one of three things: the `fastcgi_pass` socket path doesn’t match the `listen` value in `www.conf`, PHP-FPM isn’t running, or the socket’s `listen.owner`/`listen.group` don’t allow Nginx to connect. Check both the Nginx error log and the PHP-FPM journal.

How do I choose between `dynamic`, `static`, and `ondemand`? Use `dynamic` for most sites — it scales workers with demand. Use `static` for high, steady traffic where predictable RAM use matters. Use `ondemand` for low-traffic or memory-tight servers, since it runs no idle workers.

Do I need to restart PHP-FPM after editing php.ini? Yes. `php.ini` is read at process start, so changes to it require `sudo systemctl restart php8.3-fpm`. Reloading Nginx alone does nothing for PHP settings.

About the Author

Leave a Reply