Reverse proxy vs load balancer
A reverse proxy is a server that accepts requests on behalf of one or more backend servers and forwards them, so clients only ever talk to the proxy. A load balancer spreads requests across several backends. Load balancing is one of the jobs a reverse proxy can do, which is why the terms overlap.
The software is often the same: Nginx, HAProxy, Envoy and the load balancers cloud providers sell can all proxy and balance. A forward proxy is the opposite arrangement. It acts for clients, such as a company proxy that employees' browsers send their traffic through.
What a reverse proxy takes off your application
Putting a proxy in front of an application moves shared work into one place.
- It terminates TLS. The proxy holds the certificate and decrypts HTTPS, then forwards plain HTTP over a private network, or opens a new TLS connection when that network is not trusted.
- It routes requests by hostname and path, for example sending /api to an API service and everything else to the web app.
- It can cache responses and compress them with gzip or Brotli on the way out.
- It adds and removes headers: forwarded headers for the backend, security headers such as Strict-Transport-Security for the browser, and internal headers stripped before responses leave.
- It enforces limits on request size, timeouts and request rates, and buffers slow clients so backends are not tied up waiting for them.
- Backends no longer need public IP addresses. That reduces exposure, but attacks inside valid requests, such as SQL injection, pass straight through unless you add filtering.
Layer 4 and layer 7 load balancing
The layer numbers come from the OSI model. A layer 4 load balancer works with TCP or UDP connections: it sees IP addresses and ports and forwards bytes without reading them. It is fast and works for any protocol, including databases and TLS that the backend terminates itself, but it cannot route by URL or header.
A layer 7 load balancer understands the application protocol, usually HTTP. It can route by host, path, header or cookie, retry a failed request on another backend, and add headers. To read HTTPS requests it has to terminate TLS, and it spends more CPU per request than a layer 4 balancer.
Behind a layer 4 balancer, the backend may see the balancer's address as the source of every connection. Some balancers preserve the client address. Others pass it with the PROXY protocol, a short header sent at the start of each connection.
Load-balancing algorithms
The algorithm decides which healthy backend gets the next request or connection.
- Round robin sends requests to each backend in turn. It works well when requests cost about the same and backends are the same size.
- Weighted round robin sends more traffic to larger backends, or a small share to a new version during a canary release.
- Least connections picks the backend with the fewest open connections. It suits traffic whose duration varies widely, such as long uploads or WebSockets.
- Hashing on the client IP or on a key such as a session ID sends the same client to the same backend. Consistent hashing limits how many clients move when backends are added or removed.
Health checks and sticky sessions
A load balancer only helps if it stops sending traffic to backends that cannot answer. Active health checks request an endpoint such as /health every few seconds. They remove a backend after a set number of failures and add it back after a set number of successes. Passive checks watch real traffic for errors and timeouts.
A health endpoint should report whether this instance can serve requests. If it also checks a shared dependency such as the database, one database problem marks every backend unhealthy at once. The balancer then has nowhere left to send traffic.
Sticky sessions pin each client to one backend, usually with a cookie. They make load uneven and lose the session when that backend goes away. With session state in a shared store, such as a database or Redis, any backend can serve any request.
Forwarded headers and who to trust
Behind a proxy, the backend sees every connection coming from the proxy's address. Proxies pass the original details in headers: X-Forwarded-For carries the client IP, X-Forwarded-Proto the original scheme and X-Forwarded-Host the original host. RFC 7239 standardizes the same information in one header, for example Forwarded: for=203.0.113.7;proto=https.
Each proxy appends the address the connection came from, so the X-Forwarded-For list grows from left to right. A client can also send the header itself, which makes the leftmost entries whatever the client chose to write. In the example below, the client sent 198.51.100.23, and the site's proxy appended 203.0.113.7, the address it received the connection from.
If your app trusts the leftmost value, anyone can fake their IP address and get past IP-based rate limits, allowlists and audit logs. Configure your application or framework with the addresses of your own proxies. It should read the header from the right, skip those trusted addresses, and treat the first untrusted address as the client.
X-Forwarded-Proto causes a different problem. An app behind a TLS-terminating proxy receives plain HTTP, and if it ignores the header, it can redirect every request to HTTPS in an endless loop.
GET /account HTTP/1.1
Host: shop.example.com
X-Forwarded-For: 198.51.100.23, 203.0.113.7
X-Forwarded-Proto: https