In early 2014 we had three production VPS instances running Ubuntu 12.04, Apache 2.2, and mod_php. They worked. But under load - anything above 200 concurrent connections - memory usage spiked and the servers started swapping. Apache's process model was the culprit.
Why Apache Struggles Under Load
Apache's prefork model (default with mod_php) spawns a separate process for each connection. Each process loads a full PHP interpreter. With 200 concurrent connections, that's 200 PHP-loaded Apache processes running simultaneously.
On a 2GB VPS:
Apache process with mod_php: ~25-40MB RAM each
200 concurrent × 35MB = 7GB required
Available: 2GB
Result: swap, OOM killer, 502s
Nginx's architecture is different: one master process + a small number of worker processes handle thousands of connections through non-blocking I/O. PHP execution is offloaded to PHP-FPM (FastCGI Process Manager) - a separate pool of PHP workers.
The Migration
Step 1: Install Nginx and PHP-FPM (side by side with Apache first)
# Ubuntu 12.04
sudo apt-get install nginx php5-fpm
# Don't stop Apache yet - test Nginx on port 8080 first
Step 2: PHP-FPM Configuration
; /etc/php5/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /var/run/php5-fpm.sock ; Unix socket - faster than TCP for same-server
; Process manager: dynamic (adjust to your RAM)
pm = dynamic
pm.max_children = 20 ; Max PHP workers
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.max_requests = 500 ; Restart workers after N requests (prevent memory leaks)
; Timeouts
request_terminate_timeout = 30s
Step 3: Nginx Virtual Host Config
# /etc/nginx/sites-available/mysite.com
server {
listen 80;
server_name mysite.com www.mysite.com;
root /var/www/mysite/public;
index index.php index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 256;
# Static files - serve directly, don't touch PHP
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# PHP files
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 30;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
}
# Pretty URLs (for frameworks like CodeIgniter, Laravel)
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
}
Step 4: Cut Over
# Stop Apache, start Nginx
sudo service apache2 stop
sudo update-rc.d apache2 disable
sudo service nginx start
sudo service php5-fpm start
# Watch error log for first 10 minutes
sudo tail -f /var/log/nginx/error.log
What Broke
PHP session path: Apache and PHP-FPM run as different users. Session files written by Apache were unreadable by PHP-FPM's www-data user. Fix: chown -R www-data:www-data /var/lib/php5/sessions
$_SERVER['REMOTE_ADDR']: Nginx passes the real IP in HTTP_X_FORWARDED_FOR, not REMOTE_ADDR. Fix: add fastcgi_param REMOTE_ADDR $remote_addr; to fastcgi params.
.htaccess files: Nginx doesn't read .htaccess. All rewrite rules from .htaccess had to be manually translated into Nginx location blocks. Took about 2 hours per site.
Before vs After
| Metric | Apache + mod_php | Nginx + PHP-FPM |
|---|---|---|
| RAM at idle | 480MB | 95MB |
| RAM at 200 concurrent | 2.1GB (swap) | 420MB |
| Requests/sec (static files) | 800 | 8,400 |
| Requests/sec (PHP) | 280 | 320 |
| Time to first byte (avg) | 180ms | 95ms |
Static file performance was the biggest surprise - Nginx serving static assets is dramatically faster than Apache because there's zero PHP overhead. Images, JS, and CSS served from Nginx directly cut server load significantly.
What We Run Today
The nginx + php-fpm pattern held for years. By 2018 we were containerizing everything with Docker. By 2020 we'd mostly moved to Node.js and Next.js. But:
- The Nginx config structure is essentially the same
- PHP-FPM's pool configuration concepts map directly to container resource limits
- The principle - separate your web server from your application runtime - is foundational
If you're still on Apache + mod_php in 2024, the migration is worth doing. The concepts are the same, the packages are more mature, and the performance gain is immediate.
Aunimeda provides DevOps engineering and infrastructure services - CI/CD pipelines, containerization, cloud deployments, and monitoring setups.
Contact us to discuss your infrastructure needs. See also: DevOps Services, Custom Software Development