By default most LAMP tutorials wire PHP into Apache via mod_php — it works, but it loads a PHP interpreter into every Apache worker process whether that worker is serving PHP or a static image. Switching to PHP-FPM (FastCGI Process Manager) hands PHP off to its own dedicated pool of processes, freeing Apache workers to do what they are good at and letting you tune PHP concurrency independently. By the end of this guide Apache will be passing every .php request to a PHP-FPM Unix socket, mod_php will be fully disabled, and your server will handle significantly more concurrent traffic on the same hardware.
Prerequisites
- Ubuntu 22.04 LTS (steps are nearly identical on 20.04 and Debian 12)
- Apache 2.4 already installed and running — run
apache2 -vto confirm - PHP already installed — this guide uses PHP 8.2; adjust the version number throughout if yours differs
- Root or passwordless sudo access
- At least one Apache virtual host configured and serving a PHP file so you can verify the switch works
Step 1 — Install PHP-FPM
Install the FPM variant of your PHP version from the ondrej/php PPA (already on your system if you installed PHP from there) or from the default Ubuntu repos.
sudo apt update
sudo apt install php8.2-fpm -y
Confirm the service started cleanly:
sudo systemctl status php8.2-fpm
You should see active (running). PHP-FPM creates a Unix socket at /run/php/php8.2-fpm.sock by default — you will point Apache at this socket in a later step.
Step 2 — Enable the Required Apache Modules
PHP-FPM communicates with Apache over FastCGI. You need two Apache modules: proxy_fcgi (the FastCGI proxy) and setenvif (used by the standard FPM configuration snippet). Enable them now:
sudo a2enmod proxy_fcgi setenvif
Note:
mod_proxyis pulled in automatically as a dependency ofproxy_fcgi. You do not need to enable it separately.
Step 3 — Disable mod_php
Running mod_php and PHP-FPM at the same time causes conflicts — Apache will try to handle .php files twice. Disable mod_php for PHP 8.2 (and any other PHP version you have loaded) before going further.
sudo a2dismod php8.2
If you previously had another PHP version loaded, disable that too. For example:
sudo a2dismod php8.1
Verify no php module remains enabled:
apache2ctl -M | grep php
The command should return nothing. If you still see a php8.x_module listed, run a2dismod for that version before continuing.
Step 4 — Enable the PHP-FPM Apache Configuration
When you installed php8.2-fpm, it dropped a ready-made Apache configuration snippet into /etc/apache2/conf-available/. Enable it with:
sudo a2enconf php8.2-fpm
Take a look at what the conf file contains so there are no surprises:
cat /etc/apache2/conf-available/php8.2-fpm.conf
You will see something like the following. The FilesMatch block matches every .php request and the SetHandler directive routes it to the FPM Unix socket:
FilesMatch ".php$"
SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
/FilesMatch
No further edits are needed for a standard single-site setup.
Step 5 — Restart Apache and PHP-FPM
Reload both services to apply every change made so far:
sudo systemctl restart php8.2-fpm
sudo systemctl restart apache2
Check that Apache came back up cleanly:
sudo systemctl status apache2
Gotcha: If Apache fails to start, run
sudo apache2ctl configtestto pinpoint the bad line before you dig through logs. A common culprit is a virtual host still referencing amod_phpdirective such asphp_valueorphp_flag— those directives only work withmod_phpand must be moved into aphp.inior poolwww.confoverride when using FPM.
Step 6 — Verify PHP Is Running Through FPM
Create a temporary info file in your web root to confirm which PHP SAPI is active. Replace /var/www/html with your actual document root if it differs.
echo "php phpinfo();" | sudo tee /var/www/html/info.php
Open http://your-server-ip/info.php</code> in a browser and search the page for the Server API row. It must read FPM/FastCGI — not Apache 2.0 Handler, which would mean mod_php is still active.
Once confirmed, delete the file immediately — leaving a public phpinfo page on a production server is a security risk:
sudo rm /var/www/html/info.php
Step 7 — Tune the PHP-FPM Pool for Your Server
The default FPM pool configuration is conservative. Open the pool file for editing:
sudo nano /etc/php/8.2/fpm/pool.d/www.conf
The three most impactful settings for a typical VPS with 2 GB RAM are:
pm = dynamic — keeps a variable number of workers, scaling up under load and releasing memory when idle. Leave this as-is unless you have profiled a reason to use static or ondemand.pm.max_children — the hard cap on concurrent PHP workers. A safe starting formula: available RAM in MB divided by average PHP process size in MB. Check your average process size with the command below. For a 2 GB server running a typical WordPress or Laravel app, 20 to 30 is a reasonable starting point.pm.max_requests — restart a worker after this many requests to prevent memory leaks from accumulating. Set it to 500 as a safe default.
Check your average PHP process size in MB:
ps --no-headers -o rss -C php-fpm8.2 | awk '{sum+=$1} END {print sum/NR/1024 " MB"}'
Then set the pool values accordingly:
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
After editing, reload FPM to apply without dropping active connections:
sudo systemctl reload php8.2-fpm
Wrap-up
Apache is now fully decoupled from the PHP interpreter. Every .php request flows through a Unix socket to a dedicated PHP-FPM worker pool, while Apache workers stay lean and handle static assets without carrying PHP overhead. The practical gains — lower memory per Apache process, tunable PHP concurrency, and per-pool user isolation — make PHP-FPM with Apache the right default for any production LAMP server. From here, the natural next steps are enabling OPcache inside /etc/php/8.2/fpm/php.ini for bytecode caching, and setting up a separate FPM pool per virtual host if you are running multiple applications on the same box.