Key Architecture Takeaways
- ›High-Concurrency Decoupling: Nginx handles 100,000+ asynchronous TCP sockets in a single worker process via
epoll, protecting backend Apache worker threads from mobile client latency. - ›Kernel-Level Static Caching: Static assets (HTML, CSS, JS, WebP) are served directly by Nginx using the Linux kernel
sendfile()syscall in< 1mswithout touching Apache processes. - ›Origin Security Perimeter: Apache origin is bound strictly to loopback
127.0.0.1:8080, rendering backend application ports completely invisible to external network attackers. - ›Transparent IP Propagation: Apache 2.4+
mod_remoteiptranslatesX-Forwarded-Forinto native client IP logging, preserving audit trails and rate limiting.
Production Ingress & Upstream Mesh Topology
In modern cloud infrastructure, combining Nginx (as an asynchronous edge reverse proxy) with Apache (as a dedicated dynamic application runner) delivers the ideal balance of high-throughput packet handling and versatile module support.
Processes PHP / Python / MySQL logic via loopback socket.
Zero-copy direct sendfile() delivery (0.8ms TTFB).
<Callout type="benchmark" title="The Concurrency Problem: Apache MPM Prefork vs Nginx Epoll"> Traditional Apache MPM Prefork allocates one dedicated operating system process (~20MB to 35MB RAM) per connection. When 1,000 mobile clients on high-latency 3G networks download files, Apache holds 1,000 processes hostage for seconds, consuming 25GB+ of system memory and freezing the daemon.
Nginx uses an asynchronous, non-blocking event-driven loop (epoll on Linux). A single worker process handles tens of thousands of simultaneous client sockets using less than 50MB of RAM, buffering responses at gigabit bus speeds and freeing backend Apache threads in 1-2 milliseconds.
</Callout>
Directory Hierarchy & File Organization
Before editing daemon settings, review the Linux configuration layout across both servers:
Step-by-Step Production Implementation
<Steps>
<Step number="01" title="Re-bind Apache to Loopback Port 8080" badge="APACHE CONF">
To allow Nginx to bind to the standard public HTTP port (80) and HTTPS port (443), reconfigure Apache to listen strictly on the loopback interface (127.0.0.1) on an unprivileged port such as 8080.
Open /etc/apache2/ports.conf and update the port binding:
sudo nano /etc/apache2/ports.conf
```
Ensure the file specifies the local loopback address:
```apache
# Restrict Apache strictly to local loopback
Listen 127.0.0.1:8080
<IfModule ssl_module>
Listen 127.0.0.1:8443
</IfModule>
```
Next, update the default VirtualHost in `/etc/apache2/sites-available/000-default.conf`:
```apache
<VirtualHost 127.0.0.1:8080>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
```
</Step>
<Step number="02" title="Enable Native Apache mod_remoteip for True Client IPs" badge="LTS LOGGING">
Because Nginx acts as an intermediary, all incoming requests to Apache arrive from `127.0.0.1`. To ensure Apache access logs, fail2ban rules, and PHP scripts record the visitor's genuine public IP, enable Apache's native `mod_remoteip` module.
<Callout type="tip" title="Modern LTS Directive: mod_remoteip Replaces Obsolete mod_rpaf">
On Ubuntu 20.04, 22.04, and 24.04 LTS, do NOT install the legacy `libapache2-mod-rpaf` package. Apache 2.4+ includes `mod_remoteip` in core, which performs zero-overhead kernel IP translation.
</Callout>
Activate the module:
```bash
sudo a2enmod remoteip
```
Create `/etc/apache2/conf-available/remoteip.conf`:
```apache
<IfModule mod_remoteip.c>
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 127.0.0.1
RemoteIPInternalProxy 10.0.0.0/8
</IfModule>
```
Enable the configuration and verify syntax before restarting:
```bash
sudo a2enconf remoteip
sudo apache2ctl configtest
sudo systemctl restart apache2
```
</Step>
<Step number="03" title="Install Nginx Edge Ingress Engine" badge="SYSTEMD">
Install Nginx from the official Ubuntu repositories and enable its systemd unit:
```bash
sudo apt update
sudo apt install -y nginx
sudo systemctl enable --now nginx
```
Verify that Nginx is listening on port 80:
```bash
sudo ss -tulnp | grep -E ':(80|8080)'
```
You should see `nginx` listening on `0.0.0.0:80` and `apache2` listening on `127.0.0.1:8080`.
</Step>
<Step number="04" title="Configure High-Performance proxy_params" badge="BUFFER TUNING">
Standardize proxy headers and buffer thresholds in `/etc/nginx/proxy_params`. This file will be included across all Nginx VirtualHosts.
```bash
sudo nano /etc/nginx/proxy_params
```
Paste the following tuned production directives:
```nginx
# Identity and Scheme Propagation
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# High-Throughput Buffer Tuning
client_max_body_size 100M;
client_body_buffer_size 1m;
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 256 16k;
proxy_busy_buffers_size 256k;
proxy_temp_file_write_size 256k;
proxy_max_temp_file_size 0;
# Sane Timeout Thresholds
proxy_connect_timeout 60s;
proxy_send_timeout 120s;
proxy_read_timeout 300s;
```
</Step>
<Step number="05" title="Configure Reverse Proxy VirtualHost with Static File Offload" badge="VHOST CONFIG">
Open the Nginx default server block at `/etc/nginx/sites-available/default`:
```bash
sudo nano /etc/nginx/sites-available/default
```
Configure Nginx to serve static files directly with zero-copy caching while forwarding all dynamic requests to the Apache backend:
```nginx
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /var/www/html;
index index.php index.html index.htm;
# Direct Static Asset Delivery (Zero-Copy Sendfile)
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
try_files $uri =404;
}
# Reverse Proxy Dynamic Traffic to Apache Backend
location / {
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/proxy_params;
}
# Deny access to sensitive hidden files (.git, .env)
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
```
</Step>
<Step number="06" title="Verify Syntax & Hot-Reload Daemons" badge="ZERO DOWNTIME">
Always test configuration syntax integrity before reloading. A syntax error in Nginx will prevent worker hot-reloading:
```bash
sudo nginx -t
```
When Nginx outputs `syntax is ok` and `test is successful`, reload the daemon gracefully:
```bash
sudo systemctl reload nginx
```
The `systemctl reload` signal instructs the Nginx master process to launch fresh worker processes with the updated configuration while allowing existing workers to drain active client sockets without dropping a single packet.
</Step>
</Steps>
---
## Complete Multi-File Configuration Reference
Compare the coordinated Apache and Nginx configurations side-by-side:
<CodeTabs>
<Tab filename="/etc/nginx/sites-available/default" language="nginx">nginx server { listen 80; listen [::]:80; server_name example.com www.example.com;
root /var/www/html; index index.php index.html;
Static assets served in 0.8ms by Nginx location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2)$ { expires 30d; add_header Cache-Control "public, max-age=2592000, immutable"; try_files $uri =404; }
Dynamic execution proxied to Apache location / { proxy_pass http://127.0.0.1:8080; include /etc/nginx/proxy_params; } }
</Tab>
<Tab filename="/etc/apache2/ports.conf" language="apache">apache # Bind strictly to loopback to prevent external WAN access Listen 127.0.0.1:8080
<IfModule ssl_module> Listen 127.0.0.1:8443 </IfModule>
</Tab>
<Tab filename="/etc/apache2/conf-available/remoteip.conf" language="apache">apache <IfModule mod_remoteip.c> # Translate X-Forwarded-For into native client IP RemoteIPHeader X-Forwarded-For RemoteIPInternalProxy 127.0.0.1 </IfModule>
</Tab>
</CodeTabs>
---
## Live Verification & Response Header Inspection
Verify that the reverse proxy is operating transparently using `curl -I`:bash curl -I -H "X-Diagnostic-Trace: 1" http://10.0.50.57
Observe the returned response headers:http HTTP/1.1 200 OK Server: nginx/1.24.0 (Ubuntu) Date: Sun, 25 Oct 2026 09:30:00 GMT Content-Type: text/html; charset=UTF-8 Connection: keep-alive Vary: Accept-Encoding X-Powered-By: Apache/2.4.58 (Ubuntu) Backend X-Real-IP-Propagated: 10.0.50.57 Cache-Control: public, max-age=86400
Notice that the public `Server` header shows **Nginx**, while the backend execution is serviced by **Apache** over the loopback interface.
---
## Production Security & Hardening Checklist
<Callout type="danger" title="Critical Security Checklist">
1. **Never bind Apache to 0.0.0.0:8080**: Always enforce `127.0.0.1:8080` to prevent direct WAN probing.
2. **Block .htaccess and .git leaks in Nginx**: Because Nginx serves static files directly, Apache's `.htaccess` protection is bypassed for static assets. Always include `location ~ /\. { deny all; }` in Nginx.
3. **Configure UFW Firewall**: Allow only ports 80, 443, and your SSH port. Port 8080 must remain closed.
</Callout>
To restrict incoming traffic strictly to Nginx ports:bash sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable ```
Official Documentation & Deep Resources
Nginx Module ngx_http_proxy_module Reference
Official Nginx proxy documentation covering proxy_pass, proxy_buffers, connection pooling, and SSL client verification.
Apache Module mod_remoteip Official Manual
Comprehensive documentation on replacing proxy IP addresses with true client IP addresses in Apache 2.4+.

