Comparison of PHP-FPM and mod_php performance on WordPress sites

PHP-FPM vs mod_php: Which Is Faster for WordPress?

, , ,

Series: WordPress Performance on DirectAdmin (Rocky Linux 9)

Phase 2: PHP & Runtime — Part 6 of 30

Overview: Why PHP Handler Choice Matters

This post is part of the “WordPress Performance on DirectAdmin (Rocky Linux 9)” series (slug=wp-perf-da), part 6 of 30, in Phase 2 (PHP & Runtime). Here we focus on the PHP handler: PHP-FPM vs mod_php.

On DirectAdmin with Rocky Linux 9, your main options are:

  • PHP-FPM (FastCGI Process Manager) behind Apache, or NGINX+Apache
  • mod_php (libapache2-style PHP module) directly in Apache workers

For WordPress, PHP-FPM is almost always faster and more resource-efficient, especially under concurrency. mod_php is simpler but ties PHP processes to Apache and makes scaling/tuning harder.

DirectAdmin on Rocky 9: What You Actually Have

On Rocky Linux 9 with a current DirectAdmin stack, the usual layouts are:

  • Apache + PHP-FPM (per-domain pools under /usr/local/php*/sockets)
  • NGINX (reverse proxy) + Apache + PHP-FPM
  • Apache + mod_php (older or simplified setups)

DirectAdmin manages PHP versions and handlers via CustomBuild. Configs live in:

  • /etc/httpd/conf/httpd.conf and /etc/httpd/conf/extra/httpd-php*.conf (Apache)
  • /etc/nginx/nginx.conf and /etc/nginx/conf.d/directadmin.conf (NGINX proxy)
  • /usr/local/directadmin/data/users/<user>/php-fpm*.conf (per-user/pool PHP-FPM)

All examples below assume:

  • Rocky Linux 9
  • systemd managed services: httpd, nginx, php-fpmXX
  • DirectAdmin CustomBuild 2.0

mod_php: How It Works and Where It Fails

Execution model

With mod_php, Apache loads PHP as a module. Each Apache worker can run PHP directly. Typical DirectAdmin/Apache config uses prefork MPM for mod_php to avoid thread-safety issues.

Key consequences:

  • Every Apache child process is “fat” (includes PHP interpreter)
  • Max concurrent PHP requests ≈ MaxRequestWorkers (or MaxClients in older configs)
  • Memory per Apache worker is high; idle workers still hold PHP memory

Performance characteristics

  • Pros
    • Simpler stack, fewer moving parts
    • Low latency for very low traffic sites
  • Cons
    • Poor concurrency scaling; memory usage explodes under load
    • Harder to isolate per-domain resource usage
    • Less compatible with NGINX reverse proxy patterns DirectAdmin prefers now

For WordPress with any real traffic, mod_php tends to hit RAM limits first, causing swap and latency spikes.

PHP-FPM: Why It Usually Wins

Execution model

PHP-FPM runs a dedicated pool of PHP worker processes and speaks FastCGI. Apache (via proxy_fcgi) or NGINX passes PHP requests to a pool.

On DirectAdmin, each user (and often each domain) has its own pool file, for example:

/usr/local/directadmin/data/users/example/php-fpm72.conf
/usr/local/directadmin/data/users/example/php-fpm81.conf

Typical pool socket path:

listen = /usr/local/php81/sockets/example.com.sock

Performance characteristics

  • Pros
    • Per-pool process limits (pm.max_children) give predictable memory usage
    • Better throughput under concurrency than mod_php
    • Per-domain tuning possible in DirectAdmin
    • Safer to use with event-based Apache MPM or NGINX
  • Cons
    • Slightly more complex debugging (one more service)
    • Misconfigured pools (too few or too many children) can hurt performance

In practice, for WordPress on Rocky 9 with DirectAdmin, PHP-FPM is the correct default. mod_php is only reasonable for tiny, low-memory, single-site boxes where simplicity beats scalability.

Checking What You’re Running Now

As root or sudoer on Rocky 9:

sudo apachectl -M | egrep 'php|proxy_fcgi|mpm'

Interpretation:

  • mod_php active:
    • Something like php_module (shared)
    • Likely mpm_prefork_module enabled
  • PHP-FPM active:
    • proxy_module and proxy_fcgi_module loaded
    • No php_module

Check PHP-FPM services:

sudo systemctl list-units 'php-fpm*'

On a typical DirectAdmin/Rocky 9 install, you’ll see something like php-fpm81.service.

Switching DirectAdmin from mod_php to PHP-FPM

Warning: This change can cause downtime if misconfigured. Perform during a maintenance window and have console access.

1. Confirm current CustomBuild settings

cd /usr/local/directadmin/custombuild
./build options | egrep 'php[0-9]|php_fpm|mod_php'

Look for lines like:

  • php1_release=8.1
  • php1_mode=mod_php or php1_mode=php-fpm

2. Plan handler change

To switch primary PHP to PHP-FPM:

cd /usr/local/directadmin/custombuild
sudo ./build set php1_mode php-fpm

If you have multiple PHP versions, set each as needed (e.g. php2_mode).

3. Rebuild Apache/PHP configs

Downtime risk: Apache reloads; PHP handler changes. Expect 10–30 seconds of disruption.

cd /usr/local/directadmin/custombuild
sudo ./build apache
sudo ./build php n
sudo ./build rewrite_confs

Then restart services:

sudo systemctl restart httpd
sudo systemctl restart php-fpm81
# If using NGINX proxy:
sudo systemctl restart nginx

Verify modules again:

sudo apachectl -M | egrep 'php|proxy_fcgi|mpm'

You should see proxy_fcgi_module and no php_module.

Per-Domain PHP-FPM Tuning in DirectAdmin

Where DirectAdmin stores pool configs

Per-user pool files are under:

/usr/local/directadmin/data/users/<user>/php-fpmXX.conf

DirectAdmin may regenerate these from templates located in:

/usr/local/directadmin/data/templates/php-fpmXX.conf
/usr/local/directadmin/data/templates/custom/php-fpmXX.conf

To keep changes persistent, use custom/ templates rather than editing generated files directly.

Good starting values for a small WordPress site

Assume:

  • 2 vCPUs
  • 4 GB RAM
  • Single medium WordPress site

Per-pool snippet (conceptual; adjust via templates):

[example.com]
user = example
group = example
listen = /usr/local/php81/sockets/example.com.sock
listen.owner = example
listen.group = example
pm = dynamic
pm.max_children = 12
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 8
pm.max_requests = 500
request_terminate_timeout = 120s

Rationale:

  • pm.max_children=12: if each PHP process uses ~80–120 MB, that’s ~960–1440 MB, plus MySQL, web server, OS. Fit within 4 GB.
  • pm.max_requests=500: recycles workers periodically to mitigate memory leaks.

Applying template-based tuning

Warning: Template mistakes can break all PHP-FPM pools. Validate syntax before reload.

  1. Copy default template to custom:
    sudo mkdir -p /usr/local/directadmin/data/templates/custom
    cd /usr/local/directadmin/data/templates
    sudo cp php-fpm81.conf php-fpm81.conf.backup
    sudo cp php-fpm81.conf custom/php-fpm81.conf
  2. Edit custom/php-fpm81.conf with your preferred editor:
    sudo vi /usr/local/directadmin/data/templates/custom/php-fpm81.conf

    Adjust the pm.* directives in the per-user section, using DirectAdmin tokens where appropriate. Keep syntax valid INI.

  3. Rebuild configs and reload:
    cd /usr/local/directadmin/custombuild
    sudo ./build php_fpm
    sudo ./build rewrite_confs
    sudo systemctl reload php-fpm81

measuring: PHP-FPM vs mod_php on Rocky 9

1. Warm up caches

  • Ensure WordPress object/page cache is enabled (covered in other phases).
  • Run a few warm-up hits:
    curl -s -o /dev/null -w '%{time_total}
    ' https://example.com/

2. Basic latency checks

Measure single-request latency before and after handler changes:

for i in {1..5}; do
  curl -s -o /dev/null -w '%{time_total}
' https://example.com/
done

Compare average values for mod_php vs PHP-FPM. You should see similar or slightly improved latency with PHP-FPM.

3. Concurrency tests with wrk

Install wrk (if not already available):

sudo dnf install -y epel-release
sudo dnf install -y wrk

Run from another host (preferred) or localhost:

wrt -t4 -c40 -d60s https://example.com/

Key metrics:

  • Requests/sec: higher is better
  • Latency distribution: look at 95th/99th percentile

PHP-FPM should maintain better latency at higher concurrency than mod_php, given correct pm.max_children.

4. Using k6 for WordPress flows

For more realistic tests (logins, cart, etc.), use k6. Install:

sudo dnf install -y k6

Example simple script (load.js):

import http from 'k6/http';
import { sleep } from 'k6';

export let options = {
  vus: 20,
  duration: '2m',
};

export default function () {
  http.get('https://example.com/');
  sleep(1);
}

Run:

k6 run load.js

Compare results between mod_php and PHP-FPM setups.

Observability: Watching PHP-FPM Under Load

System-level monitoring

During tests, observe CPU, memory, and process counts:

top -c

Look for php-fpm: pool example.com processes and track their count vs pm.max_children.

Logs

Tail web server and PHP-FPM logs while testing:

sudo tail -f /var/log/httpd/access_log /var/log/httpd/error_log
sudo tail -f /var/log/php-fpm/www-error.log 2>&1 | egrep 'slow|error'

On DirectAdmin, per-domain logs may be under:

/var/log/httpd/domains/example.com.error.log
/var/log/httpd/domains/example.com.log

WordPress-level checks (WP-CLI)

Use WP-CLI to quickly verify site health after handler changes:

cd /home/example/domains/example.com/public_html
sudo -u example -s -- wp core verify-checksums
sudo -u example -s -- wp plugin status
sudo -u example -s -- wp option get home

This confirms WordPress is still functional and paths/permissions are intact.

Practical Recommendations

When to use PHP-FPM

  • Any multi-site or multi-tenant DirectAdmin server
  • Any site with concurrent traffic > 5–10 requests/sec
  • When using NGINX reverse proxy
  • When you need per-domain resource isolation

When mod_php might be acceptable

  • Single tiny site, very low traffic
  • Legacy application requiring specific Apache/PHP module behavior
  • You fully accept scalability limitations and want minimal moving parts

For most DirectAdmin/Rocky 9 WordPress deployments, PHP-FPM is faster at scale, safer for multi-tenancy, and aligns better with current DirectAdmin templates and best practices.

Quick Checklist: Migrating Safely to PHP-FPM

  • Baseline:
    • Record current apachectl -M output
    • Measure current latency with curl and throughput with wrk or k6
  • Plan:
    • Estimate available RAM and choose initial pm.max_children
    • Schedule a maintenance window
  • Change:
    • Use DirectAdmin CustomBuild to set php*_mode=php-fpm
    • Rebuild Apache/PHP and rewrite configs
    • Restart/reload httpd, php-fpm*, and nginx if used
  • Verify:
    • Confirm proxy_fcgi_module loaded, php_module absent
    • Check logs for errors and 5xx responses
    • Run WP-CLI health checks
  • Tune:
    • Adjust pool settings via DirectAdmin templates
    • Retest with load tools, watch CPU/RAM

Note: This article offers general technical guidance. Validate all configurations in a safe environment before applying them to production.

Previous: How to Configure PHP 8.3 for Maximum WordPress Performance on DirectAdmin

Next: OPcache Explained: How to Properly Enable and Tune It for WordPress

Smart reads for curious minds

We don’t spam! Read more in our privacy policy

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *