mod_proxy & mod_proxy_balancer: Apache as a Reverse Proxy and Load Balancer

Apache In Depth

Chapter 7 · mod_proxy & mod_proxy_balancer: Apache as a Reverse Proxy and Load Balancer

Web Servers Fundamentals Chapter 6 showed a single ProxyPass/ProxyPassReverse pair pointing at one backend, and named mod_proxy_balancer without ever configuring it. This chapter is the real thing: a genuine multi-backend pool, the algorithms that decide which member handles each request, how Apache detects a failed backend, session affinity, and one very easy-to-miss directive that breaks a surprising number of real proxied applications.

A Real Backend Pool: <Proxy balancer://...>

<Proxy "balancer://mycluster"> BalancerMember "http://10.0.0.11:5000" BalancerMember "http://10.0.0.12:5000" ProxySet lbmethod=byrequests </Proxy> ProxyPass "/app" "balancer://mycluster/" ProxyPassReverse "/app" "balancer://mycluster/"

<Proxy balancer://mycluster> names a pool the same way Nginx's own upstream {} block does (Nginx In Depth Chapter 4) — each BalancerMember line is one backend, and ProxyPass/ProxyPassReverse now point at the named balancer instead of a single address. ProxySet lbmethod=byrequests picks the load-balancing algorithm.

Load Balancing Algorithms

Apache ships three real lbmethod options: byrequests (the default) distributes requests in round-robin fashion, optionally weighted per member. bytraffic balances based on bytes actually transferred rather than raw request count — a better fit when requests vary hugely in payload size, since ten tiny requests and one huge upload aren't equivalent load. bybusyness routes each new request to whichever member currently has the fewest active in-flight requests — the right choice when backend response times vary widely, since a member stuck on one slow request won't keep receiving new ones under round robin's blind rotation.

Health Checks and Member Status

When a BalancerMember fails to respond, Apache automatically marks it in an error state and stops routing new requests to it — no external health-check tool required for this basic case. By default it retries that member again after 60 seconds (configurable via a member's own retry= parameter), giving a recovering backend a chance to rejoin the pool automatically rather than needing a manual restart of the whole proxy layer.

Sticky Sessions

<Proxy "balancer://mycluster"> BalancerMember "http://10.0.0.11:5000" route=1 BalancerMember "http://10.0.0.12:5000" route=2 ProxySet stickysession=ROUTEID </Proxy>

A backend that keeps session state in its own local memory — rather than in a shared, external store — needs every request from the same client to land on the same backend instance, or that state effectively disappears mid-session. stickysession (matched against a cookie or URL parameter, here ROUTEID) pairs each client with one specific route=-tagged member and keeps routing that client there. This is a real trade-off, not a free feature: it works against true load balancing, since a client's traffic no longer distributes freely once it's pinned. Backends that instead keep session state in a shared external store (e.g. Redis) don't need sticky sessions at all — the state is available to whichever member happens to handle the next request.

ProxyPreserveHost — a small directive that breaks a surprising number of apps
By default, the Host header Apache forwards to the backend comes from the ProxyPass target URL itself (e.g. 10.0.0.11:5000), not the original Host header the client actually sent. A backend application that builds absolute URLs, checks its own configured hostname, or does any name-based routing of its own will silently misbehave — generating links pointing at the internal backend address instead of the public domain. ProxyPreserveHost On fixes this by forwarding the client's original Host header through unchanged, and is worth treating as a near-default whenever the backend app cares about its own hostname at all.
lbmethodBalances based onBest for
byrequestsRequest count (weighted round robin)Uniform, similarly-costly requests
bytrafficBytes actually transferredRequests with widely varying payload sizes
bybusynessCurrently active requests per memberBackends with widely varying response times
Watching the real, live pool state
mod_status's companion, balancer-manager (enabled at its own dedicated location, e.g. /balancer-manager), shows every BalancerMember's live status, current load, and whether Apache currently considers it healthy — the third real, live-introspection tool this course has covered, after Chapter 2's mod_status and Chapter 5's apachectl -S. All three share the same underlying lesson: check what Apache is actually doing right now, not just what the config file says it should do.

Hands-On Exercises

Exercise 1

Write a <Proxy balancer://...> block with two BalancerMembers for a backend pool where response times vary widely between requests — some backends can end up busy far longer than others on a single slow request. Choose and set the lbmethod that best fits this scenario, and explain why.

📄 View solution
Exercise 2

A backend application proxied through Apache generates password-reset emails containing links back to the site, but the links it generates point at "http://10.0.0.11:5000/reset" instead of the site's real public domain. Using this chapter's own warning box, explain what's causing this and how to fix it.

📄 View solution
Exercise 3

Explain, in your own words, the real trade-off sticky sessions introduce, what specific problem they solve, and describe one alternative approach that avoids needing sticky sessions at all.

📄 View solution

Chapter 7 Quick Reference

  • <Proxy balancer://...> + BalancerMember — Apache's real equivalent of Nginx's upstream {} block
  • lbmethodbyrequests (default), bytraffic, bybusyness
  • A failed BalancerMember is automatically marked down and retried after 60s by default — no external health checker needed for the basic case
  • stickysession pins a client to one backend for local session state, at the cost of true load distribution — a shared external session store avoids needing it
  • ProxyPreserveHost On forwards the client's real Host header to the backend instead of the proxy target's own address
  • balancer-manager shows live pool health — the third live-introspection tool in this course, after mod_status and apachectl -S