Apache as a Reverse Proxy: mod_proxy Configuration Guide
Running Apache as a reverse proxy is one of the most common production patterns for serving web applications. You get a battle-tested HTTP server handling SSL termination, compression, and caching in front of your Node.js, Python, Java, or any other backend. This guide covers practical mod_proxy configuration you can deploy today.
Why Apache as a Reverse Proxy?
Your backend application server (Gunicorn, Express, Tomcat) is good at running application code — not so good at serving the raw internet. Apache sits in front and handles:
- SSL/TLS termination so your backend speaks plain HTTP internally
- Static file serving without touching your app process
- Request buffering to protect slow backends from fast clients
- Header manipulation and URL rewriting
- Access control and rate limiting at the edge
Enabling the Required Modules
Apache ships with proxy modules disabled. Enable what you need:
# On Debian/Ubuntu
sudo a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests headers rewrite
sudo systemctl restart apache2
# On RHEL/CentOS/Rocky — edit /etc/httpd/conf.modules.d/00-proxy.conf
# Uncomment these lines:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
sudo systemctl restart httpd
Verify modules are loaded:
apache2ctl -M | grep proxy
# Expected output:
# proxy_module (shared)
# proxy_http_module (shared)
# proxy_balancer_module (shared)
Basic Reverse Proxy Configuration
The simplest case: forward all requests to a single backend.
<VirtualHost *:80>
ServerName app.example.com
# Disable forward proxying — critical for security
ProxyRequests Off
# Preserve the original Host header
ProxyPreserveHost On
# Pass X-Forwarded-For so your app sees real client IPs
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
RequestHeader set X-Forwarded-Proto "http"
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
# Timeout settings
ProxyTimeout 60
ErrorLog /var/log/apache2/app-error.log
CustomLog /var/log/apache2/app-access.log combined
</VirtualHost>
ProxyPassReverse rewrites Location, Content-Location, and URI headers in responses — without it, redirects from your backend will send clients directly to the internal address.
Path-Based Routing to Multiple Backends
Route different URL paths to different services — useful for a microservices setup or for hosting a separate API alongside a frontend:
<VirtualHost *:443>
ServerName platform.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/platform.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/platform.example.com/privkey.pem
ProxyRequests Off
ProxyPreserveHost On
# API service on port 8080
ProxyPass /api/ http://127.0.0.1:8080/api/
ProxyPassReverse /api/ http://127.0.0.1:8080/api/
# Admin panel on port 9000
ProxyPass /admin/ http://127.0.0.1:9000/
ProxyPassReverse /admin/ http://127.0.0.1:9000/
# Everything else goes to the frontend (port 3000)
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
# Strip internal headers before passing upstream
RequestHeader unset X-Internal-Token
# Set custom headers for backend identification
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"
</VirtualHost>
Order matters here: Apache matches ProxyPass directives top-to-bottom. Put more specific paths before general ones.
Load Balancing Across Multiple Backend Instances
For production deployments running multiple backend processes, use mod_proxy_balancer:
<VirtualHost *:443>
ServerName api.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/api.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/api.example.com/privkey.pem
# Define the backend pool
<Proxy "balancer://api_cluster">
BalancerMember http://10.0.1.10:8080 loadfactor=1
BalancerMember http://10.0.1.11:8080 loadfactor=1
BalancerMember http://10.0.1.12:8080 loadfactor=2 # gets twice the traffic
# Balancing algorithm
ProxySet lbmethod=byrequests
# Mark a member hot-standby (only used if all others fail)
# BalancerMember http://10.0.1.13:8080 status=+H
</Proxy>
ProxyRequests Off
ProxyPreserveHost On
ProxyPass / balancer://api_cluster/
ProxyPassReverse / balancer://api_cluster/
# Balancer manager UI — restrict to internal IPs only
<Location "/balancer-manager">
SetHandler balancer-manager
Require ip 10.0.0.0/8
Require ip 127.0.0.1
</Location>
</VirtualHost>
Balancing Methods Comparison
| Method | Directive | Best For |
|---|---|---|
| Round robin | byrequests | Uniform, stateless requests |
| Least connections | bybusyness | Variable request duration |
| Weighted | byrequests + loadfactor | Mixed-capacity backends |
| Consistent hash | bytraffic | Soft session affinity needs |
Sticky Sessions with Session Persistence
If your application isn't fully stateless, use sticky sessions to route the same client to the same backend:
<Proxy "balancer://sticky_cluster">
BalancerMember http://10.0.1.10:8080 route=node1
BalancerMember http://10.0.1.11:8080 route=node2
ProxySet lbmethod=byrequests
ProxySet stickysession=ROUTEID
</Proxy>
ProxyPass / balancer://sticky_cluster/
ProxyPassReverse / balancer://sticky_cluster/
Apache reads the ROUTEID cookie and routes accordingly. Your backend must set this cookie — or use mod_headers to inject it.
WebSocket Proxying
Modern apps often need WebSocket support. Enable mod_proxy_wstunnel:
sudo a2enmod proxy_wstunnel
# Upgrade WebSocket connections
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /ws/(.*) ws://127.0.0.1:3000/ws/$1 [P,L]
# Regular HTTP traffic
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
Timeout and Buffer Tuning
Default timeouts kill long-running requests. Tune these in your VirtualHost or globally in apache2.conf:
# How long to wait for a backend to accept a connection
ProxyTimeout 120
# How long to wait for backend response headers
# Set via the timeout parameter on BalancerMember
<Proxy "balancer://api_cluster">
BalancerMember http://10.0.1.10:8080 timeout=30 retry=5
</Proxy>
# Buffer entire request body before sending upstream
# Prevents slow-client attacks against your backend
EnableSendfile Off
ProxyIOBufferSize 65536
Debugging Proxy Issues
When something isn't routing correctly, these commands help:
# Test config syntax before reloading
apache2ctl configtest
# Watch access logs in real time
tail -f /var/log/apache2/app-access.log
# Check which virtual host is matching a request
apache2ctl -t -D DUMP_VHOSTS
# Test headers being sent to backend (install curl)
curl -v -H "Host: app.example.com" http://127.0.0.1:3000/healthz
# Check the balancer manager (if configured)
curl http://localhost/balancer-manager
Enable proxy debug logging temporarily when investigating issues:
# In your VirtualHost — remove in production
LogLevel proxy:debug proxy_http:trace3
Security Checklist
Before going live, verify these settings:
| Check | Setting | Value |
|---|---|---|
| Forward proxy disabled | ProxyRequests | Off |
| Internal headers stripped | RequestHeader unset X-Internal-* | Applied |
| Backend not directly accessible | Firewall rules | Port 8080 blocked externally |
| Balancer manager restricted | Require ip | Internal IPs only |
| TRACE method disabled | TraceEnable | Off |
| Server tokens hidden | ServerTokens | Prod |
Add these global security settings to your main Apache config:
# /etc/apache2/conf-available/security.conf
ServerTokens Prod
ServerSignature Off
TraceEnable Off
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options SAMEORIGIN
With this configuration in place, Apache handles the edge work reliably while your backend services focus on application logic.
Was this article helpful?
Related Articles
Apache mod_proxy: Reverse Proxy, Load Balancing, and WebSocket Support
How to use Apache httpd as a reverse proxy with mod_proxy — proxying to backend services, load balancing across multiple upstreams, WebSocket proxying, and health check configuration.
Apache httpd: Virtual Hosts, SSL/TLS, and URL Rewriting in Production
How to configure Apache httpd for production use — name-based virtual hosts, SSL/TLS with Let's Encrypt, HTTP to HTTPS redirects, mod_rewrite rules, and performance tuning with MPM.
Apache 301 Redirect Loop: Diagnosing And Fixing Infinite Redirect Chains In VirtualHost And .htaccess Configurations
Few things are more frustrating than deploying what you think is a clean redirect configuration, only to have your browser throw an `ERR_TOO_MANY_REDIRECTS...
Apache 413 Request Entity Too Large: Fixing File Upload Failures With LimitRequestBody And PHP Configuration
The 413 error means Apache is rejecting your upload before it even hits PHP. Here's exactly what to change and where. Two layers can block large uploads: 1...
Apache 500 Internal Server Error: Diagnosing .htaccess Syntax Failures And Module Conflicts Step By Step
If you've been running web infrastructure long enough, you know the 500 Internal Server Error is Apache's way of saying "something's broken, and I'm not go...
Apache Mod_status Real-Time Server Monitoring With Grafana And Prometheus Integration
Monitoring Apache without proper observability tooling is like flying blind. You might catch a problem after your users start complaining, but by then, you...
More in Apache
View all →Apache Mod_security WAF Rules For OWASP Top 10 Protection
If you're running Apache in production without a Web Application Firewall, you're essentially leaving your front door unlocked. mod_security is the industr...
Apache 403 Forbidden Error After Directory Permissions Fix: Troubleshooting SELinux And File Contexts
You've just spent an hour wrestling with Apache file permissions, carefully setting ownership to `apache:apache` and permissions to `755` on your directori...
Fix Apache 'AH00558: Could not reliably determine server's FQDN'
Resolve the Apache AH00558 warning about not being able to determine the server's fully qualified domain name by setting ServerName correctly.
Fix Apache mod_rewrite Not Working
Diagnose and fix Apache mod_rewrite rules not being applied, covering module loading, AllowOverride settings, .htaccess issues, and rewrite logging.