AboutBlogContact
DevOps & InfrastructureFebruary 8, 2014 4 min read 103Updated: June 22, 2026

Ditching Apache for Nginx + PHP-FPM: A Real-World Migration Story

AunimedaAunimeda
📋 Table of Contents

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

Read Also

Docker Compose vs Kubernetes: What Small Teams Actually Need in 2026aunimeda
DevOps & Infrastructure

Docker Compose vs Kubernetes: What Small Teams Actually Need in 2026

Kubernetes is powerful and over-engineered for most small products. Docker Compose is simple and hits its limits faster than you'd think. Here's where the actual boundary is, with real configs for both.

Docker and CI/CD for a Small Dev Team: What We Actually Ship in Productionaunimeda
DevOps & Infrastructure

Docker and CI/CD for a Small Dev Team: What We Actually Ship in Production

Not every team needs Kubernetes. Here's the Docker-based CI/CD setup we run for 6 production projects with a team of 8 - GitHub Actions, Docker Compose, Nginx, and zero Kubernetes.

How to Migrate an Existing Site to HTTPS with Let's Encrypt and Zero Downtime (2016)aunimeda
DevOps & Infrastructure

How to Migrate an Existing Site to HTTPS with Let's Encrypt and Zero Downtime (2016)

In 2016, Google started flagging HTTP sites in Chrome as 'Not Secure' for pages with password fields. Migrating an existing site to HTTPS required more than just installing a certificate - mixed content, canonical URLs, 301 redirects, and SEO signals all needed updating. Here's the complete migration checklist.

Need IT development for your business?

We build websites, mobile apps and AI solutions. Free consultation.

DevOps Services

Get Consultation All articles