A reverse proxy and a load balancer can sit in the same traffic path—and the same software can perform both roles—but they solve different architecture problems.
A reverse proxy controls how clients reach an application. It commonly handles TLS termination, hostname and path routing, request headers, compression, caching, rate limits, and access policy before forwarding traffic to a backend.
A load balancer controls how traffic is distributed across a pool of backends. It uses balancing rules and health information to send new requests or connections to suitable targets, remove unhealthy nodes from rotation, and support horizontal scaling or higher availability.
The practical decision is usually:
- One application backend: start with a reverse proxy.
- Two or more interchangeable backends: add load balancing.
- A public edge plus a resilient backend pool: use both roles, even if one product performs both.
This guide explains the architectural difference, Layer 4 vs Layer 7 balancing, health checks, session state, client IP handling, deployment safety, and the point at which a Raff workload should move from a single reverse proxy to a load-balanced private architecture.
Reverse proxy vs load balancer: quick decision table
| Your requirement | Reverse proxy | Load balancer | Usually need both? |
|---|---|---|---|
| Terminate HTTPS for one app server | Yes | Not required | No |
Route api.example.com and app.example.com to different services | Yes | Not required | No |
Route /api and /admin to different internal ports | Yes | Not required | No |
| Apply request-size limits, headers, compression, or caching | Yes | Sometimes at Layer 7 | Usually no |
| Spread traffic across two or more app servers | Can, if configured with an upstream pool | Yes | Often |
| Stop sending traffic to an unhealthy backend | Basic or advanced support varies | Core function | Often |
| Keep the service available during one app-node failure | Not with one backend | Yes, if healthy capacity remains | Often |
| Balance raw TCP or UDP connections | Only with transport-layer support | Yes, with Layer 4 balancing | No edge proxy may be required |
| Perform path-, host-, header-, or cookie-based routing | Yes | Yes at Layer 7 | Often one layer can perform both |
| Run blue-green or rolling deployments across nodes | Limited with one backend | Yes | Often |
| Hide databases and backend nodes from public access | Helps define the public edge | Helps expose one frontend for a backend pool | Yes, with private networking |
The important point is that reverse proxy and load balancer describe roles. Nginx, HAProxy, Envoy, Caddy, Traefik, cloud load balancers, and application gateways may combine several of these responsibilities in one implementation.
What a reverse proxy actually does
A reverse proxy accepts traffic on behalf of one or more backend services. Clients connect to the proxy, not directly to the application process.
A basic request path looks like this:
Client ↓ HTTPS Reverse proxy ↓ HTTP or HTTPS Application service
The reverse proxy becomes the application’s controlled front door.
TLS termination
The proxy can present the public certificate and decrypt incoming HTTPS traffic. Centralizing TLS reduces certificate handling inside every application process and creates one place to enforce protocol and cipher policy.
TLS termination does not automatically mean backend traffic should be unencrypted. Re-encrypt traffic to the upstream when it crosses an untrusted network, when compliance requires encryption in transit, or when defense in depth matters.
Hostname and path routing
A reverse proxy can direct requests based on application-layer information:
api.example.com → API service app.example.com → frontend service example.com/admin → admin service example.com/assets → static service
This lets several internal services share a small number of public addresses while remaining isolated on private ports or private IPs.
Header and request control
The proxy can add, remove, or normalize headers; limit request sizes; enforce timeouts; and define how client connection information is passed upstream.
This is operationally important because applications often rely on proxy-provided values for:
- original client IP;
- original protocol, such as HTTP or HTTPS;
- requested hostname;
- correlation or request IDs;
- authentication context from an upstream identity layer.
Only trust forwarding headers from proxies you control. Accepting X-Forwarded-For, Forwarded, or similar headers directly from arbitrary clients can produce false client identities and unreliable security logs.
Compression, buffering, and caching
A reverse proxy can compress responses, buffer slow clients, cache eligible content, and protect application workers from handling every network detail directly.
These features can improve performance, but they require deliberate configuration. Caching personalized or authenticated responses incorrectly can expose data. Buffering and timeout choices can also affect streaming, file uploads, server-sent events, and long-running requests.
Edge security controls
Depending on the implementation, a reverse proxy may apply:
- rate limiting;
- IP allowlists or denylists;
- authentication checks;
- web application firewall rules;
- request filtering;
- bot or abuse controls;
- security headers.
A reverse proxy reduces direct backend exposure, but it does not make the application secure by itself. Application authentication, patching, firewall policy, secrets management, and monitoring still matter.
What a load balancer actually does
A load balancer distributes requests or connections across multiple backend targets that provide the same service.
A basic multi-node path looks like this:
Client ↓ Load balancer ├── App VM 1 ├── App VM 2 └── App VM 3
The backend pool should contain nodes that can safely serve the same type of traffic. The load balancer cannot make incompatible or state-dependent servers interchangeable without help from the application architecture.
Capacity distribution
A load balancer prevents all traffic from being sent to one node while other healthy nodes sit idle. This makes horizontal scaling practical: instead of only increasing CPU and RAM on one VM, you can add more application nodes and distribute traffic across them.
This does not mean horizontal scaling is always the first answer. A single right-sized VM is often simpler and more economical. Add a load balancer when availability, deployment safety, or sustained capacity justifies the extra layer—not because a multi-node diagram looks more advanced.
Health-aware routing
A useful load balancer distinguishes between registered backends and backends that are actually ready to serve traffic.
Health checks may be:
- Passive: infer failure from real connection errors, timeouts, or bad responses.
- Active: send dedicated probes to a health endpoint or service port.
A shallow check may confirm only that a port is open. A deeper application check can verify that the process is responsive and critical dependencies are available.
Avoid making health checks too shallow or too strict:
- A check that always returns
200 OKmay keep a broken node in rotation. - A check that fails whenever a noncritical dependency is slow may remove every node at once.
- A check that performs expensive database work can create its own load problem.
Use a readiness check that answers: Can this node safely receive new traffic now?
Failover and failure isolation
When one backend fails, the load balancer can stop selecting it and continue using healthy capacity. This reduces the blast radius of an individual application-node failure.
However, a load balancer does not guarantee high availability by itself. The architecture can still fail if:
- the load balancer is a single self-hosted VM;
- all backends depend on the same failed database;
- every node is deployed on one underlying failure domain;
- a bad release breaks the entire backend pool;
- health checks misclassify healthy or unhealthy nodes;
- session state exists only on the failed node.
High availability comes from the full dependency chain, not from one component named “load balancer.”
Deployment control
A load balancer can support safer maintenance and releases by controlling which nodes receive new traffic.
A typical rolling deployment flow is:
Remove App VM 1 from rotation ↓ Drain existing connections ↓ Deploy and verify ↓ Return App VM 1 to rotation ↓ Repeat for App VM 2
Connection draining matters because immediately terminating active traffic can interrupt uploads, API requests, WebSockets, or long-running sessions. A node should stop receiving new work while existing requests are given time to finish.
This is one reason load balancing is closely connected to blue-green and rolling deployments.
Layer 4 vs Layer 7 load balancing
“Load balancer” does not describe only one type of traffic decision.
Layer 4 load balancing
Layer 4 operates at the transport layer, primarily using connection information such as:
- source and destination IP;
- source and destination port;
- TCP or UDP protocol.
It can distribute traffic without interpreting the HTTP path, hostname, cookie, or application payload.
Common Layer 4 use cases include:
- generic TCP services;
- databases or message brokers where balancing is appropriate;
- TLS passthrough;
- DNS and other UDP services;
- high-throughput connection distribution.
Layer 4 balancing is protocol-flexible and can preserve end-to-end TLS, but it cannot make content-aware routing decisions unless another application-aware layer is added.
Layer 7 load balancing
Layer 7 understands application protocols such as HTTP and HTTPS. It can route using:
- hostname;
- URL path;
- HTTP method;
- headers;
- cookies;
- application response information.
Common Layer 7 use cases include:
- routing
/apiand/appto different services; - canary or weighted HTTP releases;
- cookie-based session persistence;
- TLS termination;
- request filtering and application-aware health checks.
The trade-off is that Layer 7 has more protocol awareness, configuration, and processing responsibility.
| Decision | Layer 4 | Layer 7 |
|---|---|---|
| Sees HTTP paths and headers | No | Yes |
| Supports generic TCP/UDP | Yes | Primarily application protocols |
| Can terminate and inspect HTTP TLS | Not when using passthrough | Yes |
| Content-based routing | No | Yes |
| Typical configuration complexity | Lower | Higher |
| Good fit | Connection distribution | Application-aware traffic policy |
Why one tool can be both
The concepts overlap because many implementations accept client traffic, proxy it, and choose an upstream target in the same process.
This Nginx-style architecture demonstrates both roles:
upstream app_pool { least_conn; server 10.20.20.11:3000; server 10.20.20.12:3000; } server { listen 443 ssl; server_name app.example.com; location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://app_pool; } }
The server block performs the reverse-proxy role: it accepts public HTTPS traffic and applies request policy. The upstream pool performs the load-balancing role: it selects one of multiple application servers.
The implementation is one Nginx instance, but the architecture still contains two responsibilities.
That distinction matters when troubleshooting. A TLS or hostname-routing problem belongs to the edge proxy concern. Uneven backend load, failed health checks, or bad node selection belongs to the load-balancing concern.
Load-balancing algorithms and when they matter
The balancing algorithm determines how a healthy backend is selected.
Round robin
Requests are distributed across backends in sequence. It is a sensible default when nodes have similar capacity and requests have similar cost.
Weighted round robin
More traffic is sent to higher-capacity nodes. This is useful during migrations, mixed VM sizes, canary releases, or gradual capacity changes.
Least connections
New traffic goes to the backend with fewer active connections. It can work better when request duration varies significantly, but active connection count is not always equal to real CPU or memory load.
Hash-based routing
A value such as client IP, cookie, URL, or another request attribute determines the backend. Hashing can provide stable routing, cache locality, or basic session affinity.
Response-time or adaptive methods
Some products combine connection state, latency, and health data to select a target. These methods can be useful at scale but should not hide poor observability or unequal backend configuration.
The “best” algorithm cannot fix an application that is not safe to run across multiple nodes.
Session state: the hidden requirement behind load balancing
Load balancing works best when application nodes are stateless: any healthy node can handle the next request.
A node is not truly interchangeable when it stores important session data only in local memory or local disk.
Common state that causes problems includes:
- login sessions stored in one process;
- uploaded files written to one VM’s local disk;
- background-job ownership stored only in memory;
- local caches that contain required rather than optional data;
- WebSocket state with no reconnection or coordination strategy.
There are two common responses.
Externalize shared state
Store sessions, uploads, queues, and durable data in systems that every app node can reach, such as:
- Redis or another shared session store;
- managed or self-hosted databases;
- object storage for uploads;
- shared queues;
- coordinated pub/sub services.
This produces a cleaner multi-node architecture and makes node replacement easier.
Use session persistence carefully
Sticky sessions route a client back to the same backend. This can reduce immediate application changes, but it creates trade-offs:
- traffic may become uneven;
- the session can still fail when that node fails;
- autoscaling and deployments become harder;
- the application remains dependent on node-local state.
Session persistence is a compatibility tool, not a substitute for designing shared state deliberately. Read Stateful vs Stateless Applications before assuming that adding a load balancer automatically makes the application horizontally scalable.
WebSockets, gRPC, streaming, and long-lived connections
Not all traffic behaves like a short HTTP request.
WebSockets
The proxy or load balancer must preserve the protocol upgrade and use timeouts appropriate for long-lived connections. Rebalancing an established WebSocket connection is not the same as selecting a backend for a new request.
gRPC and HTTP/2
gRPC commonly uses HTTP/2 and long-lived multiplexed connections. A connection-level balancing decision may send many logical requests through one backend connection, so connection behavior and balancing implementation matter.
Streaming and large uploads
Buffering, request limits, idle timeouts, and connection draining can interrupt streaming responses or uploads if configured for short request-response traffic.
TCP and UDP services
Layer 7 HTTP routing is not appropriate for every protocol. Database, DNS, mail, VPN, game, or custom services may require Layer 4 proxying and protocol-specific health checks.
Choose the traffic layer after identifying the real protocol and connection lifecycle—not only the domain name.
Preserving the real client IP
Once a proxy or load balancer sits in front of the application, the backend may see the proxy’s private IP instead of the original client address.
Common mechanisms for passing client connection information include:
- the standardized
ForwardedHTTP header; X-Forwarded-Forand related de facto headers;- the PROXY protocol for connection-level metadata.
Configure the backend to trust these values only when they arrive from known proxy addresses. Otherwise a client may submit a forged forwarding header and appear to originate from another IP.
Client IP handling affects:
- application logs;
- rate limiting;
- geolocation;
- abuse detection;
- allowlists;
- audit trails;
- incident investigation.
Test it before production. “The app works” is not enough if every request is logged as the load balancer’s IP.
Architecture patterns from one VM to multiple nodes
Pattern 1: one VM with a reverse proxy
Internet ↓ HTTPS Nginx or Caddy on Raff VM ↓ localhost Application process
Use this when:
- one VM has enough capacity;
- the application can tolerate that VM as one failure domain;
- you need TLS, routing, and a clean public edge;
- operational simplicity matters more than node-level failover.
A reverse proxy is valuable here. A dedicated load balancer usually is not.
Pattern 2: one proxy VM and several backend processes on the same VM
Internet ↓ Reverse proxy ├── App process 1 ├── App process 2 └── App process 3
This can use multiple CPU cores and isolate application processes, but it is still one VM. It improves process-level resilience, not infrastructure-level availability.
Pattern 3: public load-balancing layer with private app VMs
Internet ↓ Load-balancing edge ↓ private network App VM 1 App VM 2 ↓ private network Database, Redis, workers
Use this when:
- more than one app VM serves the same workload;
- node failure should not take the entire application offline;
- rolling maintenance or horizontal scaling is required;
- backend services should stay off the public internet.
This is the natural point to combine Raff VMs, Private Cloud Networks, and a load-balancing layer.
Pattern 4: external edge proxy plus internal load balancer
Clients ↓ CDN, WAF, or edge proxy ↓ Regional load balancer ↓ private network Application pool
Larger systems may separate global edge concerns from regional backend distribution. Do not adopt this pattern until latency, security, multi-region, or operational requirements justify the extra layer.
Does a reverse proxy remove a single point of failure?
No—not when the reverse proxy itself runs on one VM.
A single reverse-proxy VM can protect and organize access to the application while remaining a single failure point. If it stops accepting traffic, users cannot reach healthy backends behind it.
To reduce that risk, teams may use:
- a managed load-balancing service;
- multiple proxy nodes behind another traffic layer;
- failover addresses or routing mechanisms;
- redundant infrastructure across failure domains.
The right design depends on the availability target. A small internal tool and a revenue-critical SaaS application do not need the same edge architecture.
Security and private-network design
The safest default is usually:
Public: edge proxy or load balancer on required ports Private: app nodes, databases, caches, queues, workers, monitoring
Backend nodes should normally accept traffic only from the approved balancing or proxy layer. Databases should not become public merely because several app nodes need access.
Use private networking and firewall rules to define explicit paths:
| Source | Destination | Allowed traffic |
|---|---|---|
| Internet | Public edge | TCP 80/443 as required |
| Edge or load balancer | App pool | Application port and health checks |
| App pool | Database | Database port only |
| App pool | Redis or queue | Required internal port only |
| Admin network | Infrastructure | Controlled SSH or management access |
| Monitoring | Edge and app pool | Metrics and health endpoints only |
Read Private Cloud Networks Explained and Public vs Private Cloud Traffic before exposing every node for convenience.
Common mistakes
Calling any proxy a load balancer
A proxy forwarding to one backend is not providing backend redundancy. The name of the software does not change the architecture.
Adding a load balancer before adding a second backend
This creates cost and operational work without distributing anything. Start with the simplest layer that solves the current problem.
Assuming multiple backends automatically create high availability
Shared databases, shared storage, DNS, the load balancer, and the deployment itself may still be single failure points.
Using a port-open check as application readiness
A process can accept TCP connections while being unable to serve real requests. Health checks should reflect whether new traffic is safe.
Keeping required state on local app disks
Uploads, sessions, and required job state tied to one node undermine load balancing and failover.
Trusting forwarding headers from everyone
Only known proxies should be allowed to define the original client identity.
Forgetting connection draining
Removing a node immediately can terminate active requests and persistent connections during maintenance.
Exposing every backend publicly
A load balancer should simplify the public surface, not multiply it. Keep internal nodes on private paths wherever possible.
Decision framework
Choose only a reverse proxy when:
- the application runs on one VM;
- you need HTTPS termination;
- several services share one public address;
- hostname or path routing is required;
- you need header policy, compression, buffering, or basic rate controls;
- node-level failover is not yet a requirement.
Add a load balancer when:
- two or more backend nodes provide the same service;
- one app-node failure should not stop the entire service;
- traffic exceeds the safe capacity of one right-sized VM;
- rolling or blue-green deployment traffic control is required;
- health-based backend selection is needed;
- TCP, UDP, or Layer 7 traffic must be distributed across a pool.
Use both roles when:
- the public edge needs TLS and application-aware policy;
- the backend contains multiple healthy nodes;
- internal nodes should remain private;
- the system needs both request control and failure-aware distribution.
Do not add either layer merely because:
- the architecture diagram looks more professional;
- a framework tutorial included it;
- traffic might grow someday;
- “high availability” is desired but the database and proxy remain single points of failure.
Build for the availability and traffic requirements you can define, then preserve a clear path to grow.
A practical Raff growth path
For many applications on Raff, the cleanest architecture evolves in stages.
Stage 1: one Raff VM and one reverse proxy
Run the application behind Nginx, Caddy, or another reverse proxy on a Linux VM. Terminate HTTPS, route traffic to local application ports, and keep nonpublic services closed.
Stage 2: separate stateful dependencies
Move the database, Redis, workers, or uploads into appropriate services or separate VMs. Connect internal components using Private Cloud Networks rather than public database and cache ports.
Stage 3: add another application VM
Make the application safe to run on more than one node by externalizing sessions, uploads, and durable state.
Stage 4: add load balancing
Use a load-balancing layer to distribute traffic, evaluate health, drain nodes during maintenance, and support rolling changes.
This path avoids premature complexity while keeping the architecture ready for real growth.