Why Your RAM is Vanishing: The Case for PHP-FPM
If you are still serving PHP applications using Apache's mod_php in 2012, you are voluntarily suffocating your infrastructure. I recently audited a high-traffic Magento deployment for a client in Oslo. They were throwing hardware at the problem—upgrading to 16GB RAM nodes—yet the server load average spiked to 20+ every time a marketing email went out.
The culprit? The Apache Prefork MPM. When you embed the PHP interpreter inside every Apache process, a simple request for a 1KB static image still carries the overhead of the entire PHP runtime. It is inefficient, it is archaic, and it destroys scalability.
The solution is decoupling. By switching to Nginx for event-driven connection handling and PHP-FPM (FastCGI Process Manager) for script execution, we reduced that client's memory footprint by 60% while doubling throughput. Here is exactly how to configure this stack on a CentOS 6 or Debian 6 (Squeeze) environment.
Understanding the Architecture
Unlike the monolithic Apache model, this stack separates concerns:
- Nginx: Handles thousands of concurrent connections with a tiny memory footprint. It serves static files (CSS, JS, JPG) instantly without touching PHP.
- PHP-FPM: A standalone daemon that maintains a pool of PHP workers. Nginx proxies dynamic requests to this daemon via a TCP or Unix socket.
Pro Tip: On a CoolVDS instance, always use Unix sockets (.sock) instead of TCP ports (127.0.0.1:9000) if the web server and PHP are on the same node. It saves the overhead of the TCP stack. Every microsecond counts when you are pushing 500 requests per second.
1. Installing the Stack
On RHEL/CentOS 6, the standard repositories are often outdated. I recommend the EPEL or Remi repositories to get PHP 5.3.x. PHP 5.2 is dead; do not use it.
# CentOS 6
rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-5.noarch.rpm
yum install nginx php-fpm php-common php-mysql php-pecl-apc
# Debian 6
apt-get update
apt-get install nginx php5-fpm php-apc
2. Tuning the FPM Pool
The default configuration is rarely suitable for production. It is usually too conservative. Open your pool config, typically located at /etc/php5/fpm/pool.d/www.conf or /etc/php-fpm.d/www.conf.
The most critical setting is the process manager (pm). You have two main choices: static or dynamic.
- Static: Fixed number of workers. Best for dedicated servers where you know exactly how much RAM is available for PHP.
- Dynamic: Spawns workers as needed. Safer for VPS environments where resources might fluctuate or be shared with a database.
For a CoolVDS 2GB RAM instance hosting a busy WordPress or Drupal site, use a tuned dynamic configuration:
; /etc/php-fpm.d/www.conf
[www]
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = nginx
listen.group = nginx
user = nginx
group = nginx
; Choose how the process manager controls the number of child processes.
pm = dynamic
; The maximum number of child processes to be created.
; Calculation: (Total RAM - RAM for OS/DB) / Average Process Size
; Example: (2048MB - 512MB) / 30MB per process ~= 50
pm.max_children = 50
; The number of child processes created on startup.
pm.start_servers = 10
; The desired minimum number of idle server processes.
pm.min_spare_servers = 5
; The desired maximum number of idle server processes.
pm.max_spare_servers = 15
; Terminate requests after this time to prevent memory leaks in sloppy code
request_terminate_timeout = 30s
3. Nginx FastCGI Configuration
Now, tell Nginx to pass .php files to the socket we defined above. Edit your virtual host file inside /etc/nginx/conf.d/ or /etc/nginx/sites-available/.
server {
listen 80;
server_name example.no;
root /var/www/html;
index index.php index.html;
# Serve static files directly
location ~* \.(jpg|jpeg|gif|css|png|js|ico|html)$ {
access_log off;
expires max;
}
# Pass PHP scripts to PHP-FPM
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# Connect to the socket defined in www.conf
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# Prevent timeouts on long scripts
fastcgi_read_timeout 60;
}
}
After changes, restart the services via init scripts:
service nginx restart
service php-fpm restart
4. The Secret Weapon: APC (Alternative PHP Cache)
PHP is an interpreted language. Without a bytecode cache, PHP must compile your code every single time a user loads a page. This is a waste of CPU cycles.
APC caches the compiled bytecode in shared memory. In my benchmarks on CoolVDS hardware, enabling APC reduces execution time by 300%. Check your php.ini or apc.ini:
extension=apc.so
apc.enabled=1
apc.shm_segments=1
; Allocate enough memory so you don't fragment the cache.
; 64M or 128M is usually sufficient for a CMS.
apc.shm_size=128M
; Number of seconds to cache file stats.
; Set to 0 for dev, but keep high for production I/O savings.
apc.stat=1
apc.ttl=7200
Why Infrastructure Matters
Software tuning only takes you so far. When you increase pm.max_children, you are essentially trading RAM for concurrency. If the underlying virtualization layer has slow I/O (common with oversold OpenVZ providers), your swap file will choke the system regardless of your config.
At CoolVDS, we utilize KVM virtualization. This ensures that the RAM assigned to your PHP pool is actually yours, not shared with a "noisy neighbor." Furthermore, strict data retention laws here in Norway (Personopplysningsloven) mean you need a host that respects physical data sovereignty. Latency to the NIX (Norwegian Internet Exchange) is critical for local applications; hosting in a budget US datacenter adds 100ms+ overhead that no amount of PHP tuning can remove.
Final Checklist
- Monitor logs: Check
/var/log/php-fpm/error.log. If you see "server reached pm.max_children", bump the number slightly or upgrade your RAM. - Disable unused modules: Do you need
php-gdorphp-ldap? If not, comment them out in/etc/php.d/. Save the memory. - Test load: Use
ab(Apache Bench) to simulate traffic before going live.
Performance is not an accident. It is a result of calculated architecture. Don't let default configurations kill your project.