Mastodon

How to Tune PHP-FPM and OPcache for WordPress on a VPS

  1. Home
  2. Guides
  3. How to Tune PHP-FPM and OPcache for WordPress on a...

A fresh PHP-FPM install ships with pool settings sized for nothing in particular. The default pm.max_children value is a generic placeholder, not a number calculated from your VPS's actual RAM or your site's actual PHP process size, and OPcache, if enabled at all by default, is usually running with conservative settings that undersell what it can do. Left as-is, PHP-FPM either wastes available RAM by running fewer workers than your server could support, or gets oversubscribed under a traffic spike and starts queuing requests because it was never told how many concurrent processes it could safely run.

This guide covers calculating the right number of PHP-FPM worker processes for your specific VPS, choosing the process manager mode that fits your traffic pattern, and configuring OPcache so PHP stops parsing and compiling the same files on every single request. By the end, PHP-FPM will be sized deliberately for your hardware instead of running on defaults nobody chose on purpose.

This assumes you have completed the WordPress on Nginx and PHP-FPM guide and have a working WordPress install on PHP 8.3.

Step 1 - Choosing a Process Manager Mode

PHP-FPM's pm directive controls how worker processes are managed, and the three modes fit different traffic patterns. dynamic keeps a baseline of idle workers ready and spawns more under load up to a defined maximum, which suits most WordPress sites with some regular traffic. static keeps a fixed number of workers running at all times, using more RAM constantly but avoiding the small latency cost of spawning a new worker under a sudden spike; it fits sites with steady, predictable, higher traffic. ondemand starts with zero workers and spins them up only when a request arrives, which conserves RAM on a low-traffic site at the cost of a small delay on the first request after a period of inactivity.

For a typical WordPress site on a VPS, dynamic is the right default; it is what the rest of this guide assumes. Switch to static only once you have confirmed through monitoring that traffic is consistently high enough that dynamic's spawn-and-kill cycle is itself showing up as a cost.

Step 2 - Measuring Average PHP-FPM Process Size

The right number of worker processes depends on how much RAM each one actually uses, which varies by site depending on which plugins and theme are loaded. Measure it directly rather than guessing. With the site under normal use, check the resident memory of running PHP-FPM workers:

ps --no-headers -o rss -C php-fpm8.3 | awk '{sum+=$1; count++} END {print sum/count/1024 " MB average"}'

Output:

58.3 MB average

A typical WordPress site with a handful of common plugins usually lands somewhere between 40MB and 80MB per worker; a site with WooCommerce and several heavier plugins can run higher. This number is what the next step's calculation is based on, so measure your own site rather than assuming a figure.

Step 3 - Calculating pm.max_children

pm.max_children should reflect how many PHP processes your VPS can run simultaneously without running out of memory, after leaving headroom for the operating system, Nginx, and MariaDB. A reasonable starting point reserves around 1GB for the OS, Nginx, and MariaDB combined on a small to mid-sized VPS, and divides the remainder by your measured average process size.

Using the 58MB measured above, here is how that works out across Arcadia's own VPS plans:

  • Essential (4GB RAM): ~3GB available for PHP ÷ 58MB ≈ 52 max_children
  • Standard (8GB RAM): ~7GB available for PHP ÷ 58MB ≈ 123 max_children
  • Pro (16GB RAM): ~15GB available for PHP ÷ 58MB ≈ 264 max_children

These are ceilings, not targets to fill deliberately; running near the calculated maximum under sustained load means you are one traffic spike away from memory pressure. Setting pm.max_children to roughly 80% of the calculated ceiling leaves a buffer without leaving significant capacity unused.

Step 4 - Applying the Pool Configuration

Open the pool configuration file:

sudo nano /etc/php/8.3/fpm/pool.d/www.conf

Update the pm block with your calculated value. For the 4GB example above, using 80% of the ceiling:

pm = dynamic
pm.max_children = 42
pm.start_servers = 8
pm.min_spare_servers = 5
pm.max_spare_servers = 15

pm.start_servers sets how many workers spawn immediately when PHP-FPM starts. pm.min_spare_servers and pm.max_spare_servers keep a buffer of idle workers ready between requests, so a new request does not always have to wait for a fresh process to spawn. Replace the numbers with values scaled from your own max_children calculation; a common rule of thumb is min_spare_servers around 20% of max_children and max_spare_servers around 35%.

Step 5 - Restarting and Verifying

Restart PHP-FPM to apply the new pool settings:

sudo systemctl restart php8.3-fpm

Confirm the service came back up cleanly:

sudo systemctl status php8.3-fpm

Count how many worker processes are currently running to confirm start_servers took effect:

ps --no-headers -C php-fpm8.3 | wc -l

With the pool sized correctly and confirmed running, the next step turns to OPcache, which reduces how much work each of those workers has to do per request.

Step 6 - Configuring OPcache

Without OPcache, PHP parses and compiles every PHP file on every single request, discarding the compiled result the moment the request finishes. OPcache keeps compiled bytecode in shared memory across requests, so WordPress core, theme, and plugin files are compiled once and reused until they change on disk. Open the OPcache configuration:

sudo nano /etc/php/8.3/fpm/conf.d/10-opcache.ini

Set the following values:

opcache.enable=1
opcache.memory_consumption=192
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.validate_timestamps=1

opcache.memory_consumption at 192MB gives a typical WordPress install with a moderate plugin count comfortable headroom to cache every file without evicting entries under normal use. opcache.max_accelerated_files needs to exceed the total number of PHP files across WordPress core, your theme, and all active plugins; 10000 covers this for the large majority of sites. opcache.validate_timestamps=1 with a revalidate_freq of 60 means OPcache checks whether a file has changed on disk at most once every 60 seconds rather than on every request, which is a sensible middle ground between performance and not having to manually clear the cache every time you edit a file.

Step 7 - Verifying OPcache Is Active

Restart PHP-FPM to apply the OPcache settings:

sudo systemctl restart php8.3-fpm

Create a temporary diagnostic file in the web root to confirm OPcache is running and caching files:

echo '<?php var_dump(opcache_get_status(false));' | sudo tee /var/www/yoursite.com/public/opcache-check.php

Visit https://yoursite.com/opcache-check.php in a browser. A working configuration shows opcache_enabled as bool(true) along with counters for cached scripts and memory usage. This file exposes internal server details and should not be left in place; remove it as soon as you have confirmed the output:

sudo rm /var/www/yoursite.com/public/opcache-check.php

PHP-FPM Is Now Tuned for This Server

PHP-FPM is now running a worker count calculated from your VPS's actual RAM rather than an untouched default, and OPcache is caching compiled PHP across requests instead of recompiling it every time. Revisit the pm.max_children calculation if you upgrade your VPS plan or notice average process size has changed after adding heavier plugins. From here, Redis object caching addresses the other major source of repeated work on a WordPress site: database queries, rather than PHP execution itself.

Get a VPS

Follow along with your own server. Plans from £7.98/month.

View VPS Plans

More Guides

How to Migrate WordPress from Shared Hosting to a VPS

Move a live WordPress site from shared hosting to a VPS: database export with my...

How to Set Up a Fresh Ubuntu 24.04 VPS

Step-by-step: configure a fresh Ubuntu 24.04 VPS from first SSH login to a secur...

Securing Your VPS with UFW and Fail2Ban

How to configure UFW and Fail2Ban on Ubuntu to protect your VPS from port scans...