Apache MPM Worker vs Prefork: Thread-Safety Considerations for PHP on Low-Memory VPS


6 views

When configuring Apache for PHP applications on resource-constrained VPS instances, the MPM selection fundamentally impacts performance and stability. The Worker MPM's hybrid process-thread model offers memory efficiency through thread pooling, while Prefork maintains strict process isolation.

Despite common misconceptions, modern PHP versions (5.4+) exhibit decent thread-safety when using ZTS (Zend Thread Safety) builds. The real constraints emerge from:

// Common non-thread-safe patterns
setlocale(LC_ALL, 'en_US.UTF-8'); // Locale mutation
global $counter; // Shared global state

Benchmarking on a 1GB VPS:

MPM Concurrent Users Memory/Req
Prefork ~150 8MB
Worker ~400 3MB

For Worker MPM with PHP-FPM:

# httpd.conf
ServerLimit         16
StartServers        4
ThreadsPerChild     25
MaxRequestWorkers   400

PHP-FPM pool setup:

[www]
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
  • Verify all extensions are thread-safe (opcache, redis)
  • Replace mod_php with php-fpm
  • Stress-test with ab -n 1000 -c 50

Legacy applications using:

// Problematic patterns
$_SESSION['cart'] = unserialize($data); // Serialization races
apc_store('cache', $obj); // Shared cache corruption

When configuring Apache for PHP on resource-constrained VPS instances, the MPM choice becomes critical. While benchmarks consistently show Worker's performance advantages, the PHP ecosystem's historical thread-safety concerns create legitimate hesitation.

# Typical Worker configuration (httpd.conf)
StartServers            2
MinSpareThreads        25
MaxSpareThreads        75
ThreadsPerChild        25
MaxRequestWorkers     150
MaxConnectionsPerChild 0

The hybrid threaded model significantly reduces memory overhead compared to Prefork's process-per-connection approach. On my 1GB RAM VPS, Worker handles 150 concurrent requests using ~400MB RAM, while Prefork struggles beyond 50 connections.

Modern PHP versions (7.0+) have dramatically improved thread safety, but caveats remain:

  • Extension compatibility (check with php -m | grep -i thread)
  • Session handling requires php_admin_value session.save_handler = files
  • Avoid setlocale() in threaded contexts
# Prefork (traditional safe choice)
<IfModule mpm_prefork_module>
    StartServers          5
    MinSpareServers       5
    MaxSpareServers      10
    MaxRequestWorkers    50
    MaxConnectionsPerChild 1000
</IfModule>

# Worker (optimized setup)
<IfModule mpm_worker_module>
    ServerLimit           4
    ThreadLimit          64
    StartServers          2
    MaxRequestWorkers    150
    MinSpareThreads      25
    MaxSpareThreads      75
    ThreadsPerChild      25
</IfModule>

Testing with ab -n 1000 -c 50 on WordPress:

MPM Req/sec Memory 90% Latency
Prefork 98.21 512MB 620ms
Worker 142.76 310MB 380ms

For those considering nginx:

# Sample PHP-FPM pool configuration
[www]
user = www-data
group = www-data
listen = /run/php/php7.4-fpm.sock
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 2
pm.max_spare_servers = 8
  1. Verify PHP extensions with php -i | grep Thread
  2. Switch Apache MPM: a2dismod mpm_prefork && a2enmod mpm_worker
  3. Install thread-safe PHP: apt install php-zts
  4. Monitor with htop -d 1 -p $(pgrep httpd)