Apache mod_proxy: Reverse Proxy, Load Balancing, and WebSocket Support
Apache as a Reverse Proxy
Apache httpd isn't just a file server — with mod_proxy and its companions, it's a capable reverse proxy. You can use it to proxy requests to Node.js, PHP-FPM, Tomcat, or any backend service, with built-in load balancing, session stickiness, and health checking.
Enabling Required Modules
# Ubuntu/Debian
sudo a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests headers
sudo systemctl reload apache2
# RHEL/Rocky — modules are loaded in /etc/httpd/conf.modules.d/00-proxy.conf
# Verify they're uncommented:
grep -v "^#" /etc/httpd/conf.modules.d/00-proxy.conf | grep proxy
# For WebSockets
sudo a2enmod proxy_wstunnel # Ubuntu
# Add: LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so # RHEL
Basic Reverse Proxy
Proxy all traffic to a backend application (e.g., Node.js on port 3000):
<VirtualHost *:80>
ServerName app.example.com
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
# Pass real client IP to backend
RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s
RequestHeader set X-Forwarded-Proto %{REQUEST_SCHEME}s
RequestHeader set X-Real-IP %{REMOTE_ADDR}s
ErrorLog ${APACHE_LOG_DIR}/app-error.log
CustomLog ${APACHE_LOG_DIR}/app-access.log combined
</VirtualHost>
Proxying a Subpath
Proxy only /api/ to a backend, serve everything else from the filesystem:
<VirtualHost *:443>
ServerName example.com
DocumentRoot /var/www/html
# Proxy /api/ to backend
ProxyPass /api/ http://127.0.0.1:8080/api/
ProxyPassReverse /api/ http://127.0.0.1:8080/api/
# Serve static files directly
<Directory /var/www/html>
Options -Indexes
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
Load Balancing
Distribute traffic across multiple backends using mod_proxy_balancer:
<VirtualHost *:80>
ServerName app.example.com
<Proxy balancer://appcluster>
BalancerMember http://10.0.1.10:3000 loadfactor=1
BalancerMember http://10.0.1.11:3000 loadfactor=1
BalancerMember http://10.0.1.12:3000 loadfactor=2 # gets 2x traffic
# Load balancing method
ProxySet lbmethod=byrequests
# Timeout and retry settings
ProxySet timeout=30
ProxySet retry=1
</Proxy>
ProxyPreserveHost On
ProxyPass / balancer://appcluster/
ProxyPassReverse / balancer://appcluster/
</VirtualHost>
Load Balancing Methods
| Method | Module | Behavior |
|---|---|---|
byrequests | lbmethod_byrequests | Round-robin by request count |
bytraffic | lbmethod_bytraffic | Round-robin by bytes transferred |
bybusyness | lbmethod_bybusyness | Route to least-busy worker |
heartbeat | lbmethod_heartbeat | Use heartbeat monitor data |
# Enable multiple methods (Ubuntu)
sudo a2enmod lbmethod_byrequests lbmethod_bytraffic lbmethod_bybusyness
Session Stickiness
Route the same user to the same backend:
<Proxy balancer://appcluster>
BalancerMember http://10.0.1.10:3000 route=node1
BalancerMember http://10.0.1.11:3000 route=node2
ProxySet lbmethod=byrequests
ProxySet stickysession=ROUTEID
</Proxy>
ProxyPass / balancer://appcluster/ stickysession=ROUTEID
ProxyPassReverse / balancer://appcluster/
# Inject route cookie if not set
Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
Hot Spare (Failover)
<Proxy balancer://appcluster>
BalancerMember http://10.0.1.10:3000
BalancerMember http://10.0.1.11:3000
BalancerMember http://10.0.1.12:3000 status=+H # hot standby
</Proxy>
The hot spare (+H) only receives traffic when all primary members are unavailable.
Balancer Manager
Apache provides a web UI for managing the balancer at runtime:
<Location /balancer-manager>
SetHandler balancer-manager
# Restrict to trusted IPs
Require ip 10.0.0.0/8
Require ip 127.0.0.1
</Location>
Access at http://your-server/balancer-manager to enable/disable backends, adjust load factors, and drain nodes without restarting Apache.
WebSocket Proxying
WebSocket connections require mod_proxy_wstunnel:
<VirtualHost *:443>
ServerName ws.example.com
# HTTP upgrade to WebSocket
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/ws/(.*)$ ws://127.0.0.1:3000/ws/$1 [P,L]
# Regular HTTP fallback
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
ProxyPreserveHost On
</VirtualHost>
Proxying to PHP-FPM
For PHP applications with FastCGI:
# Enable fcgi module
sudo a2enmod proxy_fcgi setenvif
<VirtualHost *:443>
ServerName php-app.example.com
DocumentRoot /var/www/php-app
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost/"
</FilesMatch>
<Directory /var/www/php-app>
Options -Indexes
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Proxying to Tomcat (AJP)
sudo a2enmod proxy_ajp
<VirtualHost *:80>
ServerName java-app.example.com
ProxyPass / ajp://127.0.0.1:8009/
ProxyPassReverse / ajp://127.0.0.1:8009/
</VirtualHost>
Health Checks
Enable the heartbeat monitor for active health checking:
<Proxy balancer://appcluster>
BalancerMember http://10.0.1.10:3000 hcmethod=GET hcuri=/health hcinterval=10 hcpasses=2 hcfails=3
BalancerMember http://10.0.1.11:3000 hcmethod=GET hcuri=/health hcinterval=10 hcpasses=2 hcfails=3
</Proxy>
hcmethod=GET— use HTTP GET to check healthhcuri=/health— the health check endpointhcinterval=10— check every 10 secondshcpasses=2— require 2 consecutive passes to mark healthyhcfails=3— require 3 consecutive failures to mark unhealthy
Timeouts and Error Handling
# Global proxy timeout settings
ProxyTimeout 120
# Per-balancer
<Proxy balancer://appcluster>
ProxySet timeout=30
ProxySet connectiontimeout=5
ProxySet retry=1
</Proxy>
# Custom error page when all backends are down
ErrorDocument 503 /maintenance.html
Useful Debug Headers
Add response headers to identify which backend served the request:
Header add X-Backend-Server %{BALANCER_WORKER_NAME}e
Header add X-Balance-Method %{BALANCER_ALGORITHM}e
These headers help trace which node handled a request during debugging — remove in production or restrict to internal IPs.
# Quick test
curl -I https://app.example.com/
# Look for X-Backend-Server header
Was this article helpful?
Network & Traffic Engineer
Packets don't lie. I design and troubleshoot the network layer that everything else depends on — Nginx, Envoy, HAProxy, DNS, CDNs, and everything in between. If it touches a socket, it's my problem.
Related Articles
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 as a Reverse Proxy: mod_proxy Configuration Guide
Configure Apache as a reverse proxy using mod_proxy to route traffic to backend applications with load balancing and health checks.
Envoy Proxy: Architecture, xDS Configuration, and Getting Started
An introduction to Envoy Proxy's architecture — listeners, clusters, filters, and the xDS dynamic configuration API. Covers static configuration for standalone use and how Envoy fits into service meshes like Istio.
HAProxy Configuration Guide: TCP/HTTP Load Balancing, Health Checks, and SSL
A comprehensive HAProxy configuration guide covering frontends, backends, balance algorithms, active health checks, SSL termination, ACLs, rate limiting, and the runtime socket API.
HAProxy + Keepalived: Production HA Load Balancer Setup
Step-by-step guide to building a highly available load balancer pair with HAProxy and Keepalived — covering the full stack from VIP configuration to health checks, stats, and SSL termination.
Keepalived and VRRP: Building High-Availability Failover for Linux Services
How to configure Keepalived for automatic failover using VRRP — setting up master/backup pairs, virtual IPs, health-check scripts, and combining it with HAProxy or Nginx for zero-downtime load balancer HA.
More in Nginx
View all →Envoy Traffic Management: Retries, Timeouts, Canary Deployments, and Rate Limiting
Advanced Envoy traffic management — configuring retries with exponential backoff, per-request timeouts, weighted canary routing, global rate limiting, and fault injection for resilience testing.
HAProxy Advanced: SSL Termination, SNI Routing, and mTLS
Advanced HAProxy SSL configuration — multi-domain SNI routing from a single frontend, mutual TLS (mTLS) for service-to-service authentication, certificate management, and OCSP stapling.
Fix Nginx 502 Bad Gateway Behind a Reverse Proxy
Diagnose and fix Nginx 502 Bad Gateway errors when proxying to upstream backends — check sockets, timeouts, and upstream health.
Fix Nginx 'Too Many Open Files' Error
Resolve Nginx 'Too many open files' errors by increasing worker_rlimit_nofile, system ulimits, and kernel file descriptor limits.