Configure Apache HTTP Server Load Balancing with mod_proxy_balancer
Step-by-step guide to configuring Apache HTTP Server 2.4+ load balancing with mod_proxy_balancer: enable modules, define a balancer pool with health checks and weights, wire a virtual host, validate syntax, and verify request distribution. Includes diagnostic table and rollback procedure.
05 Jan 2026, 21:58 UTC

The Problem: Distributing Traffic Across Multiple Backends
When a single Apache server fronts multiple application instances, you need a way to spread incoming requests so no single backend becomes a bottleneck. Apache's mod_proxy_balancer provides this capability natively, letting you define a pool of backend servers with health checks, weighting, and optional session affinity—all without adding a separate load balancer tier.
Desired Outcome
A running Apache 2.4+ instance that accepts client requests on a virtual host and forwards them to two or more backend HTTP/HTTPS servers, automatically skipping unhealthy nodes and respecting configured load distribution weights.
Prerequisites
- Apache 2.4.x (or 2.2 with
mod_proxy_balancercompiled in) - Root or sudo access to edit
httpd.confor drop-in.conffiles under/etc/httpd/conf.d/(RHEL/CentOS) or/etc/apache2/conf-enabled/(Debian/Ubuntu) - At least two backend servers reachable via HTTP or HTTPS (e.g.,
http://app1.internal:8080,http://app2.internal:8080) - Firewall rules allowing Apache to initiate outbound connections to backend ports
- Modules
mod_proxy,mod_proxy_http,mod_proxy_balancer, andmod_slotmem_shmavailable (standard in most distributions)
Step 1: Verify and Enable Required Modules
Run the following on the Apache host to confirm the modules are present and loaded:
apachectl -M | grep -E 'proxy_(balancer|http|slotmem)'
Expected output includes lines like:
proxy_balancer_module (shared)
proxy_http_module (shared)
slotmem_shm_module (shared)
If any are missing, enable them. On Debian/Ubuntu:
a2enmod proxy proxy_http proxy_balancer slotmem_shm
systemctl reload apache2
On RHEL/CentOS/Fedora, ensure LoadModule lines exist in /etc/httpd/conf.modules.d/00-proxy.conf (or similar) and run systemctl reload httpd.
Step 2: Define the Balancer Pool
Create a dedicated configuration file (e.g., /etc/httpd/conf.d/balancer-app.conf or /etc/apache2/conf-available/balancer-app.conf then a2enconf balancer-app). Use a <Proxy> block to declare the balancer and its members:
<Proxy balancer://appcluster>
# Backend 1: weight 2, health check every 30s
BalancerMember http://app1.internal:8080 \\
loadfactor=2 \\
route=app1 \\
hcmethod=GET \\
hcuri=/healthz \\
hcinterval=30 \\
hcfails=3 \\
hcpasses=2
# Backend 2: weight 1, same health check
BalancerMember http://app2.internal:8080 \\
loadfactor=1 \\
route=app2 \\
hcmethod=GET \\
hcuri=/healthz \\
hcinterval=30 \\
hcfails=3 \\
hcpasses=2
# Optional: sticky sessions via cookie (disable if not needed)
# ProxySet stickysession=BALANCEID
</Proxy>
Key parameters explained:
loadfactor: relative weight (2 means twice as many requests as a member with 1)route: identifier used for sticky sessions; must match a cookie or URL parameter valuehcmethod/hcuri: health check method and path; backend must return 2xx/3xx within timeouthcinterval: seconds between checkshcfails/hcpasses: consecutive failures to mark down / successes to mark up
Risk: Misconfigured hcuri pointing to a non-existent endpoint will mark all backends unhealthy, causing 503 errors. Test the health endpoint directly with curl first.
Step 3: Wire the Virtual Host to the Balancer
In your virtual host (or main server config), proxy requests to the balancer:
<VirtualHost *:80>
ServerName app.example.com
# Preserve client IP for backend logging
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "http"
# Forward all traffic to the balancer
ProxyPass / balancer://appcluster/
ProxyPassReverse / balancer://appcluster/
# Optional: expose balancer status at /balancer-manager (protect in production!)
<Location /balancer-manager>
SetHandler balancer-manager
Require ip 10.0.0.0/8 192.168.0.0/16
</Location>
</VirtualHost>
ProxyPassReverse rewrites Location, Content-Location, and URI headers from backend responses so redirects point to the public hostname, not the internal backend URL.
Step 4: Validate Configuration and Reload
Before applying changes, run a syntax check:
apachectl configtest
Output should be Syntax OK. Then perform a graceful restart to avoid dropping active connections:
apachectl -k graceful
# or
systemctl reload httpd
Expected Checks: Verify Distribution and Health
1. Confirm Module Load
apachectl -M | grep proxy_balancer
Should return proxy_balancer_module (shared).
2. Test Health Endpoint Directly
curl -I http://app1.internal:8080/healthz
curl -I http://app2.internal:8080/healthz
Both must return 200 OK (or 2xx/3xx).
3. Observe Request Distribution
From a client machine, issue multiple requests and note the backend via a custom header or log correlation:
for i in {1..10}; do curl -s -o /dev/null -w "%{http_code} " http://app.example.com/; done
With loadfactor=2 and 1, roughly 2/3 of requests should hit app1, 1/3 app2. Check backend access logs to confirm.
4. Inspect Balancer Status (if enabled)
curl -s http://app.example.com/balancer-manager | grep -A2 'appcluster'
The HTML table shows each member's status (OK/Down), load factor, elected count, and last health check result.
Limitations and Caveats
- No TLS termination on backends shown: If backends require HTTPS, use
BalancerMember https://...and ensure Apache trusts their certificates (or setProxySSLVerify offwith caution). - Sticky sessions can skew load: Only enable
ProxySet stickysession=...if your application truly requires server-side session state. Otherwise, leave disabled for even distribution. mod_proxy_balancerhealth checks are passive-active hybrid; they do not replace a dedicated layer-4/7 health checker for critical infrastructure.- Configuration changes to
BalancerMemberlines require a graceful restart; runtime changes viabalancer-managerare temporary until config reload.
Recovery: Roll Back a Broken Config
If the reload causes 503s or loops:
- Disable the balancer include:
mv /etc/httpd/conf.d/balancer-app.conf /etc/httpd/conf.d/balancer-app.conf.bak(ora2disconf balancer-app) - Run
apachectl -k graceful - Verify service returns to prior state (e.g., direct
ProxyPassto a single backend or static content) - Fix the configuration, test with
apachectl configtest, then re-enable and reload.
Quick Diagnostic Decision Tree
| Symptom | Likely Cause | Check |
|---|---|---|
| All requests return 503 | Health checks failing | curl -I http://backend/healthz; verify hcuri, firewall, backend process |
| Uneven distribution despite weights | Sticky sessions enabled | Check ProxySet stickysession; disable if not needed |
| Redirects go to internal backend URL | Missing ProxyPassReverse | Add ProxyPassReverse / balancer://appcluster/ |
| Config test passes but reload fails | Module not loaded at runtime | apachectl -M | grep proxy_balancer |
Verification Checklist
- [ ]
apachectl configtestreturnsSyntax OK - [ ]
apachectl -M | grep proxy_balancershows module loaded - [ ] Backend
/healthzendpoints return 2xx directly - [ ] 10+ requests via public hostname distribute per
loadfactorratio - [ ]
balancer-manager(if exposed) shows all members OK - [ ] Graceful restart completes without error in
error_log
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.