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.
Use a reverse proxy when you need a controlled application edge for TLS, host/path routing, headers, buffering, caching, or request policy. Use load balancing when traffic must be distributed across two or more interchangeable backends with health-aware routing and failure capacity.
For Raff Technologies workloads, a common growth path is one Raff VM behind Nginx or Caddy first, then private backend networking and a traffic-distribution layer only when multiple application nodes are justified. This guide treats load balancing as an architecture pattern rather than assuming Raff's managed Load Balancer product is generally available.
Reverse proxy vs load balancer: quick comparison
| Requirement | Reverse proxy | Load balancer |
|---|---|---|
| Terminate HTTPS | Strong fit | Common at Layer 7 |
| Route by hostname/path | Strong fit | Layer 7 only |
| Add/normalize headers | Strong fit | Often at Layer 7 |
| Buffer/compress/cache | Common | Implementation-dependent |
| Distribute across multiple backends | Possible with upstream pools | Core role |
| Remove unhealthy backends | Implementation-dependent | Core role |
| Support one-node failover | No, not with one backend | Yes, if healthy capacity remains |
| Balance raw TCP/UDP | Only with transport support | Layer 4 fit |
| Drain nodes during deployment | Possible with suitable proxy | Common load-balancing function |
| Keep app backends private | Yes | Yes |
The key idea is that reverse proxy and load balancer describe roles, not necessarily separate products. Nginx, HAProxy, Envoy, Caddy, Traefik, and cloud traffic services can combine both responsibilities.
What is a reverse proxy?
A reverse proxy accepts client traffic on behalf of an application and forwards it to one or more backend services.
Client ↓ HTTPS Reverse proxy ↓ HTTP/HTTPS Application
The proxy becomes the controlled public front door while the application process can listen only on localhost or a private network address.
Typical reverse-proxy responsibilities include:
- TLS termination;
- hostname and path routing;
- forwarding headers;
- request-size limits;
- buffering;
- compression;
- caching;
- rate limits;
- access rules;
- authentication integration;
- logging and request IDs.
A reverse proxy is valuable even when there is only one backend.
What is a load balancer?
A load balancer distributes requests or connections across a pool of backend targets that provide the same service.
Client ↓ Traffic-distribution layer ├─ App VM 1 ├─ App VM 2 └─ App VM 3
Its main jobs are to:
- select a healthy backend;
- spread traffic across the pool;
- stop using unhealthy targets;
- preserve enough capacity after a failure;
- support maintenance and deployment without sending new traffic to a draining node.
A load balancer is useful only when the backend pool is actually interchangeable enough to serve the same workload.
The main difference between a reverse proxy and a load balancer
The simplest distinction is:
A reverse proxy controls how traffic enters an application. A load balancer controls which backend receives that traffic.
With one backend:
Internet → reverse proxy → app
With several interchangeable backends:
Internet → reverse proxy / load-balancing layer → app pool
The second design may use one implementation for both functions.
Nginx reverse proxy vs Nginx load balancing
Nginx is a useful example because the same software can perform both roles.
A single-backend reverse proxy:
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://127.0.0.1:3000; } }
This controls the public edge but does not distribute requests across multiple servers.
An upstream pool adds load balancing:
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 public server block performs the reverse-proxy role. The upstream pool performs the load-balancing role.
This is why calling every reverse proxy a load balancer is inaccurate. A proxy forwarding to one backend provides no backend redundancy.
Reverse proxy load balancing: when one layer does both jobs
A Layer 7 proxy can often handle both request policy and backend distribution.
That design is practical when you need:
- HTTPS termination;
- hostname/path routing;
- multiple application nodes;
- health-aware upstream selection;
- request headers and client-IP forwarding;
- gradual deployment traffic control.
The benefit is operational simplicity: one traffic layer can own several HTTP concerns.
The risk is concentration. If that traffic layer is one self-hosted VM, it remains a single failure point even when the application pool behind it has several healthy nodes.
Layer 4 vs Layer 7 changes what the traffic layer can see
Layer 4 and Layer 7 load balancing solve different problems.
| Capability | Layer 4 | Layer 7 |
|---|---|---|
| Typical traffic | TCP, UDP, TLS passthrough | HTTP/HTTPS |
| Routing basis | IP, port, protocol, connection | Host, path, header, cookie, method |
| HTTP awareness | No | Yes |
| Raw protocol support | Strong | Limited to understood application protocols |
| TLS inspection | Not with passthrough | Common with termination |
| Typical fit | Generic connection distribution | Websites, APIs, microservices |
Choose Layer 4 when the system should distribute transport connections without interpreting the application request.
Choose Layer 7 when HTTP-aware routing or edge request policy matters.
Use Load Balancing Explained for the deeper L4/L7, health-check, draining, and failover model.
TLS termination is usually a reverse-proxy concern
A reverse proxy commonly owns the public certificate and terminates HTTPS before forwarding the request upstream.
Three common models are:
| Model | Path |
|---|---|
| Termination | HTTPS → proxy, HTTP → protected backend |
| Re-encryption | HTTPS → proxy, HTTPS → backend |
| Passthrough | TLS remains encrypted to backend |
Termination simplifies certificate management and enables application-aware routing. Re-encryption protects the backend hop. Passthrough keeps TLS termination at the application but limits Layer 7 inspection at the traffic layer.
Do not assume private traffic means encryption is unnecessary. Use TLS where workload sensitivity, customer requirements, or trust boundaries justify it.
Health checks are what make a backend pool operational
A load balancer should send new traffic only to backends that are ready to serve production requests.
A port-open check is often too shallow. A useful readiness endpoint should answer:
Can this backend safely receive new traffic right now?
Good readiness checks are:
- fast;
- deterministic;
- inexpensive;
- strict enough to remove a broken node;
- not dependent on every optional third-party service.
Health checks that are too shallow keep broken nodes in rotation. Checks that are too strict can remove every node together.
Connection draining matters during deployments
Removing a backend from a pool should not immediately terminate active requests or long-lived connections.
A safer sequence is:
- Stop sending new traffic to the backend.
- Allow routing state to update.
- Let active requests finish.
- Stop or update the application.
- Run readiness checks.
- Return the backend to rotation.
This is especially important for uploads, streaming, WebSockets, long API requests, and rolling deployments.
Use Blue-Green vs Rolling Deployments when designing the release process.
Session state determines whether backends are interchangeable
Load balancing works best when any healthy backend can handle the next request.
Local state that breaks interchangeability includes:
- sessions stored only in application memory;
- uploads written only to one VM disk;
- required cache data that exists on one node;
- background jobs owned only by local process state;
- local durable application files.
Prefer shared or external state where appropriate:
| Local dependency | Better multi-node pattern |
|---|---|
| In-memory sessions | Shared session store or suitable token model |
| User uploads | Object storage or approved shared storage |
| Durable app files | External authoritative storage |
| Job state | Shared queue with retry/ownership rules |
| Database | Separate database service or VM |
Sticky sessions can temporarily preserve compatibility, but they do not make the application stateless and do not protect a user when the selected node fails.
Use Stateful vs Stateless Applications before scaling the application tier horizontally.
Client IP and forwarding headers need an explicit trust model
Once a proxy sits in front of an application, the backend may see the proxy address instead of the original client address.
Common mechanisms include:
Forwarded;X-Forwarded-For;X-Forwarded-Proto;- the PROXY protocol for connection metadata.
Trust these values only from known traffic-layer addresses. If the application accepts arbitrary client-supplied forwarding headers, logs, rate limits, geolocation, allowlists, and abuse detection may use forged source information.
Test client-IP handling before production.
WebSockets, gRPC, streaming, and uploads need protocol-aware settings
A traffic layer configured for short HTTP requests can break long-lived or streaming connections.
For WebSockets, confirm:
- protocol upgrade handling;
- idle timeouts;
- connection draining behavior;
- reconnect strategy.
For gRPC and HTTP/2, remember that many logical requests may share longer-lived connections.
For large uploads or streaming responses, review buffering, request-size limits, timeouts, and drain periods.
For raw TCP or UDP services, Layer 7 HTTP routing may be the wrong tool entirely.
A reverse proxy does not remove a single point of failure by itself
One Nginx VM in front of two healthy app servers is still one traffic-entry failure point.
Internet ↓ Single proxy VM ← still one failure boundary ↓ App VM 1 + App VM 2
If the availability target requires the edge itself to survive failure, use an architecture with redundant traffic entry or a suitable managed traffic service.
If you plan to depend on a managed Raff Load Balancer, verify its current product availability first. This guide does not assume it is generally available.
Private backends reduce unnecessary public exposure
A multi-node application should not require every application server to expose its service port publicly.
A better pattern is:
Internet ↓ Public reverse proxy / traffic layer ↓ private VPC App VM 1 App VM 2 ↓ private VPC Database / cache / workers
Raff VPC provides private, unmetered traffic between supported Raff resources without a separate VPC charge.
Use firewall rules so backend ports accept traffic only from approved service roles and administration paths.
Use VPC Architecture for Multi-VM Applications for CIDR, routing, isolation, and private-service design.
Databases should not become public because the app scaled out
Adding application nodes may create more database connections and more query concurrency, but it does not justify exposing the database to the internet.
Keep the database on a private path and allow only approved application and administration sources.
If a team does not want to operate the data layer directly, Raff provides Managed Databases for PostgreSQL, MySQL, Valkey, ClickHouse, and Kafka.
Architecture patterns from one VM to multiple backends
Pattern 1 — One Raff VM with a reverse proxy
Internet ↓ HTTPS Nginx / Caddy on Raff VM ↓ localhost Application
Good fit when:
- one VM has enough capacity;
- node-level failover is not required;
- TLS and routing are the main needs;
- simplicity matters.
Pattern 2 — One VM with several app processes
Internet ↓ Reverse proxy ├─ App process 1 ├─ App process 2 └─ App process 3
This can use several CPU cores, but it remains one VM and one infrastructure failure domain.
Pattern 3 — Multi-VM application with private backends
Internet ↓ Traffic-distribution layer ↓ Raff VPC App VM 1 App VM 2 ↓ Managed DB / private DB / shared services
Good fit when:
- multiple app VMs are justified;
- one-node failure should not stop the service;
- rolling maintenance matters;
- backends should remain private.
Pattern 4 — External edge plus internal service distribution
Larger systems may separate CDN/WAF/public edge concerns from regional or internal backend distribution.
Do not add this architecture until security, scale, latency, or operational requirements justify the extra layer.
Reverse proxy vs load balancer decision framework
Use a reverse proxy first when
- one application VM is sufficient;
- HTTPS termination is required;
- host/path routing is needed;
- request headers or access policy need control;
- backend redundancy is not yet a requirement.
Add load balancing when
- two or more interchangeable backends serve the same workload;
- one app-node failure should not cause a complete outage;
- one VM is no longer enough for sustained capacity;
- rolling or blue-green traffic control is required;
- health-aware backend selection is needed.
Use both roles when
- the public edge needs TLS and application-aware policy;
- the backend has multiple healthy nodes;
- nodes should remain private;
- request control and failure-aware distribution are both required.
Do not add complexity only because
- a framework tutorial included it;
- traffic might grow someday;
- the diagram looks more production-like;
- the database and traffic layer remain single failure points anyway.
How to build this on Raff today
A practical Raff path is:
Stage 1 Raff VM Nginx/Caddy reverse proxy Application Stage 2 Raff VPC Separate database / workers / private services Stage 3 Second interchangeable app VM Externalized sessions/uploads/state Stage 4 Traffic-distribution layer Health checks Draining Failure-capacity testing
Current Raff building blocks relevant to this design include:
- Raff VM for application and proxy workloads;
- Linux VM for Linux-based Nginx/Caddy deployments;
- VPC for private backend traffic;
- Security for the surrounding security model;
- Object Storage for shared uploads/assets where appropriate;
- Managed Databases for a separate data layer;
- Data Protection for VM recovery planning.
Current Raff VM public traffic uses a 3 Gbps connection with unmetered VM traffic and no VM egress fee. Current VPC traffic is private and unmetered, with no separate VPC charge.
Do not make a production design dependent on Raff's managed Load Balancer unless its current availability and required features have been verified.