Placing Nginx in front of Apache as a reverse proxy combines the strengths of both web servers: Nginx efficiently handles static content and TLS termination, while Apache continues to process dynamic requests with full support for .htaccess files and mod_rewrite rules. This architecture is common in shared hosting environments and on servers where you want performance gains without abandoning Apache's features.
Why Use Nginx as a Reverse Proxy for Apache
Apache remains popular for its flexibility, extensive module ecosystem, and per-directory configuration through .htaccess files. However, Nginx excels at serving static files and handling concurrent connections with lower memory overhead. By positioning Nginx as a frontend proxy, you gain several advantages:
- Static content acceleration: Nginx serves images, CSS, JavaScript, and other static assets directly from disk without invoking Apache.
- TLS offloading: Nginx handles SSL/TLS encryption and decryption, freeing Apache from cryptographic overhead.
- Connection pooling: Nginx maintains persistent connections to clients while using fewer backend connections to Apache.
- Protection: Nginx acts as a buffer against slow clients and certain attack patterns.
- Caching layer: Optional caching can be added at the Nginx layer without modifying Apache configuration.
The tradeoff is added complexity: you now manage two web servers, and troubleshooting requires understanding both layers.
When This Architecture Makes Sense
Consider Nginx as a reverse proxy when:
- You rely on .htaccess files or Apache modules that have no direct Nginx equivalent.
- Your application or CMS assumes Apache-style configuration.
- You want performance improvements but cannot migrate fully to Nginx.
- You run a shared hosting environment where users expect Apache compatibility.
- Static content comprises a significant portion of your traffic.
This setup is less useful if you can rewrite all configuration into native Nginx directives or if your application is already optimized for Nginx. A full migration to Nginx alone will always be simpler than running both servers.
Architecture Overview
In this configuration:
- Nginx listens on the public IP address on ports 80 and 443.
- Apache listens only on localhost (127.0.0.1) on a non-standard port, typically 8080.
- Nginx serves static files directly from the document root.
- Nginx proxies dynamic requests (PHP, CGI, server-side logic) to Apache on localhost:8080.
- Apache processes the request, applies .htaccess rules, and returns the response through Nginx.
From the client perspective, only Nginx is visible. Apache sees requests as originating from 127.0.0.1, so you must configure proper IP forwarding headers.
Prerequisites
Before starting, ensure:
- Both Nginx and Apache are installed on your server.
- You have root or sudo access.
- Apache is currently serving your sites on port 80 or 443.
- You have a backup of your Apache and Nginx configurations.
- Firewall rules allow traffic on ports 80 and 443.
Step 1: Reconfigure Apache to Listen on Localhost
First, move Apache off the public ports. Edit the Apache ports configuration file. On Debian/Ubuntu systems, this is typically /etc/apache2/ports.conf. On RHEL/CentOS, check /etc/httpd/conf/httpd.conf.
Change the Listen directive from:
Listen 80
To:
Listen 127.0.0.1:8080
If Apache is also listening on 443 for HTTPS, change:
<IfModule ssl_module>
Listen 443
</IfModule>
To:
<IfModule ssl_module>
Listen 127.0.0.1:8443
</IfModule>
You can omit the HTTPS port on Apache entirely if Nginx will handle all TLS termination, which is the recommended approach.
Next, update each virtual host. Find all VirtualHost directives and change them to bind to the localhost port:
<VirtualHost 127.0.0.1:8080>
ServerName example.com
DocumentRoot /var/www/example.com
# existing configuration
</VirtualHost>
Restart Apache and verify it is listening only on localhost:
sudo systemctl restart apache2
sudo ss -tlnp | grep :8080
You should see Apache bound to 127.0.0.1:8080, not 0.0.0.0:8080.
Step 2: Install and Configure Nginx
If Nginx is not yet installed:
# Debian/Ubuntu
sudo apt update && sudo apt install nginx
# RHEL/CentOS
sudo yum install nginx
Create a new Nginx server block for your site. Place it in /etc/nginx/sites-available/example.com (Debian/Ubuntu) or /etc/nginx/conf.d/example.com.conf (RHEL/CentOS).
Here is a basic configuration:
upstream apache_backend {
server 127.0.0.1:8080;
}
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.php index.html;
access_log /var/log/nginx/example.com_access.log;
error_log /var/log/nginx/example.com_error.log;
# Serve static files directly
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Proxy dynamic requests to Apache
location / {
proxy_pass http://apache_backend;
proxy_set_header Host $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;
}
}
Key Directives Explained
- upstream: Defines the Apache backend. You can add multiple servers here for load balancing if needed.
- root: Must match the DocumentRoot in your Apache VirtualHost so Nginx can serve static files from the same directory.
- location ~* .(...)$: Matches static file extensions. Nginx serves these directly without passing to Apache. Adjust the regex to match your assets.
- proxy_set_header: Passes client information to Apache. Without these headers, Apache logs will show 127.0.0.1 as the client IP.
Enable the site (Debian/Ubuntu):
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
Test the configuration and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
Step 3: Preserve Client IP Addresses in Apache Logs
Because Apache now sees all requests from 127.0.0.1, you must extract the real client IP from the X-Forwarded-For header. Enable mod_remoteip or mod_rpaf.
Using mod_remoteip (Apache 2.4+)
Enable the module:
sudo a2enmod remoteip
Create a configuration file /etc/apache2/conf-available/remoteip.conf:
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 127.0.0.1
Enable it:
sudo a2enconf remoteip
sudo systemctl restart apache2
Update your LogFormat to use %a instead of %h:
LogFormat "%a %l %u %t \"%r\" %>s %b" common
Apache will now log the real client IP address.
Step 4: Add TLS Termination at Nginx
To handle HTTPS, configure Nginx to terminate TLS and proxy to Apache over plain HTTP on localhost.
Obtain an SSL certificate using Let's Encrypt:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Certbot will automatically modify your Nginx configuration to add the SSL server block. Alternatively, add it manually:
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
root /var/www/example.com;
index index.php index.html;
access_log /var/log/nginx/example.com_ssl_access.log;
error_log /var/log/nginx/example.com_ssl_error.log;
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
location / {
proxy_pass http://apache_backend;
proxy_set_header Host $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 https;
}
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
Reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
Your site now serves HTTPS through Nginx while Apache handles dynamic content over HTTP on localhost.
Step 5: Test and Verify
Check that static files are served by Nginx:
curl -I https://example.com/style.css
Look for Server: nginx in the response headers.
Check that dynamic content is proxied to Apache:
curl -I https://example.com/index.php
Verify that .htaccess rules still apply. If you have rewrite rules or access controls, test them. Apache processes .htaccess files as usual; Nginx simply passes matching requests through.
Check Apache logs to confirm real client IPs are logged:
sudo tail -f /var/log/apache2/example.com_access.log
Tuning and Optimization
Adjust Proxy Buffers
For better performance with large responses, tune Nginx proxy buffers:
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
Add Caching
Nginx can cache proxied responses:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m;
location / {
proxy_cache my_cache;
proxy_cache_valid 200 60m;
proxy_cache_use_stale error timeout updating;
proxy_pass http://apache_backend;
# other proxy headers
}
Be cautious with caching dynamic content; add proxy_cache_bypass rules for logged-in users or admin panels.
Increase Worker Connections
Edit /etc/nginx/nginx.conf:
events {
worker_connections 2048;
}
Reload Nginx after changes.
Troubleshooting
502 Bad Gateway
Nginx cannot reach Apache. Verify Apache is running and listening on 127.0.0.1:8080:
sudo systemctl status apache2
sudo ss -tlnp | grep 8080
Check Nginx error logs:
sudo tail -f /var/log/nginx/error.log
Static Files Not Found
Ensure the root directive in Nginx matches Apache's DocumentRoot and that Nginx has read permissions:
sudo ls -ld /var/www/example.com
.htaccess Rules Not Working
If rules fail, verify AllowOverride is enabled in Apache:
<Directory /var/www/example.com>
AllowOverride All
</Directory>
Restart Apache and test again.
Redirect Loops
If your application or .htaccess forces HTTPS, ensure X-Forwarded-Proto is set correctly in Nginx. Apache must see the request as HTTPS even though the backend connection is HTTP.
Conclusion
Running Nginx as a reverse proxy in front of Apache offers a practical middle ground: you keep Apache's flexibility and compatibility while gaining Nginx's performance for static assets and TLS termination. The setup requires coordinating two web servers, but for environments where .htaccess support or Apache modules are non-negotiable, this architecture delivers measurable improvements in concurrency and resource efficiency. Start with a single site, validate the configuration thoroughly, and expand to additional domains once you are confident in the setup.
