The Apache mod_php Era is Over: Scaling with PHP-FPM and Nginx
If you are still running PHP 5.3 or 5.4 via Apache's mod_php prefork module on a high-traffic site, you are essentially setting money on fire. The architecture of embedding the PHP interpreter inside every Apache process is a memory disaster waiting to happen. In the Norwegian hosting market, where bandwidth at NIX (Norwegian Internet Exchange) is plentiful but hardware resources come at a premium, efficiency is not optional.
I recently audited a Magento installation for a client in Oslo. Their server was crashing daily at 10:00 AM. top showed Apache consuming 14GB of RAM just to serve static assets because every request—even for a simple JPEG—was dragging a heavy PHP interpreter along with it. The solution wasn't adding more RAM. The solution was decoupling.
Why PHP-FPM (FastCGI Process Manager)?
PHP-FPM separates the web server from the PHP processing. This allows you to use a lightweight event-driven server like Nginx to handle thousands of concurrent connections for static files (CSS, images, JS) with a negligible memory footprint, while passing only dynamic PHP requests to the FPM application server. This is the architecture we enforce on all high-performance CoolVDS deployments.
Step 1: The Nginx Configuration
First, we need Nginx to talk to the FPM socket. Sockets (.sock) are generally faster than TCP/IP (127.0.0.1:9000) because they avoid the overhead of the networking stack, provided your web server and PHP are on the same machine.
Here is a battle-tested Nginx virtual host configuration for PHP 5.4:
server {
listen 80;
server_name example.no;
root /var/www/example.no/public_html;
index index.php index.html;
# Optimize static file serving
location ~* \.(jpg|jpeg|gif|css|png|js|ico|html)$ {
access_log off;
expires max;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# The critical socket connection
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Buffer settings to prevent writing to disk
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
}
}
Step 2: Tuning the FPM Pool
The default configuration in Debian Squeeze or CentOS 6 is far too conservative. The most important settings reside in /etc/php5/fpm/pool.d/www.conf. The default pm = dynamic is often useful, but for predictable high-load systems, pm = static prevents the overhead of forking processes under load.
Pro Tip: If you have plenty of RAM (e.g., 16GB+ on a CoolVDS plan), usepm = static. This keeps all children alive and ready. If you are on a smaller VPS, stick todynamicto free up RAM for the database.
Here is a configuration optimized for a server with 4GB RAM dedicated mostly to web serving:
[www]
user = www-data
group = www-data
listen = /var/run/php5-fpm.sock
; Set to static for max performance if RAM allows
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
; Kill scripts that run too long to prevent resource exhaustion
request_terminate_timeout = 60s
; Log slow requests to identify bad code
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 5s
; Prevent memory leaks by respawning workers periodically
pm.max_requests = 500
The Hidden Bottleneck: Disk I/O and Sessions
You can tune your configuration files all day, but if your underlying storage is spinning rust (HDD), your PHP-FPM workers will sit in a D state (Uninterruptible Sleep), waiting for the disk. This is common with PHP session files being written to /var/lib/php/session.
In 2012, standard VPS providers typically offer SAN storage based on 15k SAS drives. While decent, they cannot handle the random I/O of thousands of session writes. At CoolVDS, we utilize enterprise-grade SSDs. The difference is moving from 150 IOPS to 30,000+ IOPS. If you cannot migrate to SSDs yet, move your sessions to Memcached to keep them in RAM.
Offloading Sessions to Memcached
Edit your php.ini to stop writing files to disk:
session.save_handler = memcached
session.save_path = "127.0.0.1:11211"
Opcode Caching: APC is Not Optional
PHP 5.4 does not include a built-in opcode cache by default. Without one, PHP compiles your script into opcodes on every single request. This is CPU suicide. You must install APC (Alternative PHP Cache).
Run pecl install apc and configure /etc/php5/conf.d/apc.ini:
extension=apc.so
apc.enabled=1
apc.shm_segments=1
; Allocate enough memory so you don't fragment the cache
apc.shm_size=128M
; Cache entries for 2 hours
apc.ttl=7200
apc.user_ttl=7200
; Verify file changes only on dev, disable on prod for speed
apc.stat=0
Legal Compliance in Norway
When tuning logs in nginx.conf, remember Datatilsynet regulations. If you are logging IP addresses in your access logs, you are processing PII (Personally Identifiable Information). Ensure your log rotation policy is strict. We recommend rotating logs daily and keeping them no longer than necessary for security auditing.
Performance is about the entire stack: from the SSD backing the filesystem, to the Nginx worker processes, down to the APC opcode cache. Don't let legacy Apache configurations kill your uptime.
Ready to test real throughput? Spin up a CoolVDS SSD instance in Oslo today and see how PHP-FPM performs when it isn't waiting on a hard drive.