Scaling Beyond Apache: The Battle-Hardened Guide to Nginx Reverse Proxying
It is 3:00 AM. Your pager is screaming because your primary web server just hit the MaxClients limit again. You check top and see Apache spawning child processes like a rabbit colony, eating up every megabyte of swap until the OOM killer starts shooting blindly. We have all been there. Most hosting providers will tell you the solution is to upgrade to a larger VPS plan. They are wrong.
The problem isn't your hardware; it is your architecture. In the current landscape of 2010, relying solely on the prefork MPM of Apache for high-concurrency sites is a suicide mission. While Apache is excellent for processing dynamic PHP content, it is terrible at holding open thousands of idle keep-alive connections. Enter Nginx.
I have spent the last month migrating a high-traffic Norwegian media portal from a pure LAMP stack to an Nginx reverse proxy setup. The results? Memory usage dropped by 60%, and we handled a traffic spike during the recent cross-country skiing championships without a single dropped packet. Here is how you do it properly, referencing the specific constraints of the Norwegian infrastructure.
The Architecture: Nginx as the Bouncer
Think of Nginx as the bouncer at a club, and Apache as the bartender. You don't want the bartender greeting people at the door; you want them pouring drinks (executing PHP). Nginx handles the static files (images, CSS, JS) and manages client connections asynchronously. It only passes requests to Apache when actual server-side processing is needed.
This setup is critical for latency, especially if your target audience is in Oslo or Bergen. Even a few milliseconds of delay in handshake overhead adds up.
1. Installation (The Real Way)
Don't just yum install from the default repositories; they are often outdated. For CentOS 5.5, I recommend adding the EPEL repository or compiling Nginx 0.8.53 stable from source to get the latest stub_status module.
# Download and compile (if you need specific modules)
wget http://nginx.org/download/nginx-0.8.53.tar.gz
tar zxvf nginx-0.8.53.tar.gz
cd nginx-0.8.53
./configure --with-http_stub_status_module --with-http_ssl_module
make && make install
2. The Configuration Strategy
The magic happens in nginx.conf. We need to define an upstream block that points to our backend Apache server (usually running on port 8080 or bound to 127.0.0.1).
Here is a battle-tested configuration block designed for stability:
http {
# Basic optimization
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# The Backend Definition
upstream backend_hosts {
server 127.0.0.1:8080 weight=1;
}
server {
listen 80;
server_name www.example.no;
# serve static files directly
location ~* ^.+.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js)$ {
root /var/www/html;
expires 30d;
}
# Pass dynamic content to Apache
location / {
proxy_pass http://backend_hosts;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Critical timeouts to prevent hanging connections
proxy_connect_timeout 60;
proxy_send_timeout 90;
proxy_read_timeout 90;
}
}
}
Pro Tip: Always setproxy_set_header X-Real-IP. Without this, your Apache logs will show all traffic coming from 127.0.0.1, making IP-based security bans via.htaccesscompletely useless.