---
title: Reverse Proxy and Load Balancing
url: https://doc.liz6.com/en/networking/06-HTTP/04-reverse-proxy-and-load-balancing
locale: en
area: networking
tags:
- networking
- HTTP
date: 2026-06-30
modified: 2026-07-16
description: Reverse proxy is more than just "forwarding requests"—TLS termination, caching, compression, rate limiting, health checks, and blue-green deployments all converge on traffic management at this layer. The configuration philosophies of nginx, Caddy, and HAProxy differ significantly.
---

# Reverse Proxy and Load Balancing

> Reverse proxy is more than just "forwarding requests"—TLS termination, caching, compression, rate limiting, health checks, and blue-green deployments all converge on traffic management at this layer. The configuration philosophies of nginx, Caddy, and HAProxy differ significantly.

## Overview

A reverse proxy acts as a server-side facade—it receives all external requests, handles TLS termination, SNI routing, rate limiting, and logging, then forwards requests to backend services. A load balancer distributes requests across multiple backend instances according to strategies (round-robin, least connections, IP hash), thereby improving availability and throughput. Caddy is known for automatic TLS (ACME + Let's Encrypt with zero configuration), while nginx and HAProxy dominate enterprise environments with high performance and rich L4/L7 features. Understanding reverse proxies is a prerequisite for deploying any internet-facing service.

## Reverse Proxy vs. Forward Proxy

<svg viewBox="0 0 720 320" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Comparison of request flow between forward proxy and reverse proxy">
  <defs><marker id="rpah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="320" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Forward Proxy vs. Reverse Proxy: Who is proxying whom</text>

  <text x="40" y="54" font-size="13" font-weight="700" fill="#334155">Forward Proxy</text>
  <rect x="40" y="62" width="80" height="34" rx="6" fill="#e2e8f0"/>
  <text x="80" y="83" text-anchor="middle" font-size="12" fill="#334155">Client</text>
  <line x1="120" y1="79" x2="156" y2="79" stroke="#475569" stroke-width="1.6" marker-end="url(#rpah)"/>
  <rect x="160" y="62" width="80" height="34" rx="6" fill="#cbd5e1"/>
  <text x="200" y="83" text-anchor="middle" font-size="12" fill="#334155">Proxy</text>
  <line x1="240" y1="79" x2="276" y2="79" stroke="#475569" stroke-width="1.6" marker-end="url(#rpah)"/>
  <rect x="280" y="62" width="90" height="34" rx="6" fill="#e2e8f0"/>
  <text x="325" y="83" text-anchor="middle" font-size="12" fill="#334155">Target</text>
  <text x="40" y="114" font-size="11" fill="#64748b">Example: Local mihomo :7890 → proxy browser traffic to overseas</text>

  <line x1="40" y1="130" x2="680" y2="130" stroke="#e2e8f0" stroke-width="1"/>

  <text x="40" y="150" font-size="13" font-weight="700" fill="#0f766e">Reverse Proxy</text>
  <rect x="40" y="160" width="70" height="36" rx="6" fill="#ccfbf1"/>
  <text x="75" y="182" text-anchor="middle" font-size="11" fill="#115e59">Client</text>
  <line x1="110" y1="178" x2="146" y2="178" stroke="#475569" stroke-width="1.6" marker-end="url(#rpah)"/>
  <rect x="150" y="160" width="170" height="36" rx="6" fill="#f0fdfa" stroke="#99f6e4" stroke-dasharray="4 3"/>
  <text x="235" y="175" text-anchor="middle" font-size="11" fill="#0f766e">grafana.liz6.com</text>
  <text x="235" y="190" text-anchor="middle" font-size="9.5" fill="#0f766e">(Looks like Target)</text>
  <line x1="320" y1="178" x2="356" y2="178" stroke="#475569" stroke-width="1.6" marker-end="url(#rpah)"/>
  <text x="338" y="170" text-anchor="middle" font-size="9.5" fill="#64748b">Actually</text>
  <rect x="360" y="160" width="110" height="36" rx="6" fill="#0d9488"/>
  <text x="415" y="175" text-anchor="middle" font-size="11" font-weight="600" fill="#ffffff">Caddy:443</text>
  <text x="415" y="190" text-anchor="middle" font-size="9.5" fill="#ffffff">(Proxy)</text>
  <line x1="470" y1="178" x2="506" y2="178" stroke="#475569" stroke-width="1.6" marker-end="url(#rpah)"/>
  <rect x="510" y="160" width="140" height="36" rx="6" fill="#ccfbf1" stroke="#99f6e4"/>
  <text x="580" y="175" text-anchor="middle" font-size="11" fill="#115e59">127.0.0.1:3000</text>
  <text x="580" y="190" text-anchor="middle" font-size="9.5" fill="#115e59">(Backend)</text>
  <text x="40" y="214" font-size="11" fill="#64748b">The client thinks it is connecting directly to Grafana and doesn't know it's on localhost</text>

  <rect x="40" y="236" width="640" height="56" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="56" y="258" font-size="12.5" fill="#115e59">A forward proxy helps the client access the external network and hides the client from the target; a reverse proxy does the opposite,</text>
  <text x="56" y="278" font-size="12.5" fill="#115e59">pretending to be the target itself to the client, while hiding the real backend behind it.</text>
</svg>

## Caddy Configuration

```caddy
grafana.liz6.com {
    reverse_proxy 127.0.0.1:3000 {
        header_up Host {upstream_hostport}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }
}

chat.liz6.com {
    reverse_proxy 127.0.0.1:3001
}
```

Responsibilities of a reverse proxy:
1. **TLS Termination**: Caddy handles TLS → backend HTTP → backend doesn't need to handle TLS
2. **SNI Routing**: Route to different backends on the same port (443) based on SNI
3. **Request Rewriting**: Add X-Forwarded-* headers, modify path/headers
4. **Rate Limiting**: Per-client or per-endpoint rate limiting
5. **Access Logging**: Centralized logging (Caddy JSON access log)

## L4 vs. L7 Load Balancing

<svg viewBox="0 0 720 360" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Comparison of L4 and L7 load balancing principles">
  <rect width="720" height="360" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">L4 vs. L7: Whether to parse HTTP determines routing capability and speed</text>

  <rect x="40" y="44" width="310" height="26" rx="6" fill="#94a3b8"/>
  <text x="195" y="61" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">L4 · TCP (does not parse HTTP)</text>
  <rect x="40" y="80" width="310" height="40" rx="6" fill="#f1f5f9" stroke="#e2e8f0"/>
  <text x="52" y="104" font-size="10.5" fill="#475569">nginx stream module / HAProxy mode tcp</text>
  <rect x="40" y="130" width="310" height="56" rx="6" fill="#f1f5f9" stroke="#e2e8f0"/>
  <text x="52" y="152" font-size="11" font-weight="600" fill="#334155">Does not parse HTTP → only looks at TCP payload → but very fast</text>
  <text x="52" y="172" font-size="10.5" fill="#64748b">Cannot route based on URL / Host header</text>
  <rect x="40" y="196" width="310" height="48" rx="6" fill="#dcfce7" stroke="#4ade80"/>
  <text x="52" y="216" font-size="11" font-weight="700" fill="#166534">Advantage: Very fast</text>
  <text x="52" y="234" font-size="10.5" fill="#15803d">Suitable for raw TCP forwarding, WebSocket pass-through</text>

  <rect x="370" y="44" width="310" height="26" rx="6" fill="#4f46e5"/>
  <text x="525" y="61" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">L7 · HTTP (parses and routes based on content)</text>
  <rect x="370" y="80" width="310" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="382" y="104" font-size="10.5" fill="#3730a3">nginx http module / HAProxy mode http / Caddy</text>
  <rect x="370" y="130" width="310" height="72" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="382" y="148" font-size="10.5" font-weight="600" fill="#3730a3">Parses HTTP → can route based on Host / URL /</text>
  <text x="382" y="164" font-size="10.5" font-weight="600" fill="#3730a3">Header / Cookie / Method</text>
  <text x="382" y="182" font-size="10" fill="#4338ca">Can perform content-based rate limiting, request inspection</text>
  <rect x="370" y="212" width="310" height="48" rx="6" fill="#ffedd5" stroke="#f97316"/>
  <text x="382" y="232" font-size="11" font-weight="700" fill="#9a3412">Cost: Slightly slower</text>
  <text x="382" y="250" font-size="10.5" fill="#c2410c">HTTP parsing overhead</text>

  <rect x="40" y="282" width="640" height="56" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="56" y="304" font-size="12.5" fill="#3730a3">Choose L4 or L7 depending on whether content-based routing is needed: use L4 for pure forwarding and WebSocket passthrough to prioritize speed;</text>
  <text x="56" y="324" font-size="12.5" fill="#3730a3">to split traffic based on Host / Path / Header or perform content-level rate limiting, you must use L7.</text>
</svg>

## Load Balancing Algorithms

```
Round Robin:         Round-robin across backends
Least Connections:   Select backend with fewest connections (L7 only)
Random:              Random selection
IP Hash:             Same client IP → same backend → sticky
Consistent Hash:     Hash ring → only 1/N remap when backends change
Weighted:            Backend weight → weight=3 gets 3x traffic

Health Check:
  passive: Detect failures from actual request errors (max_fails=N)
  active:  Periodic GET /health → expect 200 → mark as down on failure
  slow_start: Gradually increase traffic after backend recovery (avoid flood)
```

## References

- **Caddy**: caddyserver.com/docs/caddyfile/directives/reverse_proxy
- **nginx**: nginx.org/en/docs/http/ngx_http_upstream_module.html
- **HAProxy**: haproxy.org

*Keywords: reverse proxy, SNI routing, L4/L7 LB, health check, sticky session, rate limiting, Caddy*
