Fundamentals

Proxy vs VPN vs NAT vs load balancer

Four middleboxes compared by OSI layer, what they rewrite, whether they terminate connections, and what the origin server actually sees as the client IP.

· 12 min read · How we verify this

Key points

  • The dividing test is connection termination. A proxy is an endpoint twice; NAT, DSR load balancers and routed VPNs rewrite or encapsulate packets in flight.
  • A NAT gateway cannot return 502, because it has no application-layer voice. If a box can answer in the protocol's own language, it terminated the connection.
  • An L4 load balancer is a proxy in proxy mode (HAProxy mode tcp, IPVS NAT with a userspace relay) and is not one in direct server return.
  • NAT preserves the client's TCP fingerprint and IP TTL behaviour; a proxy replaces both with its own stack's, which changes what the origin can infer.

A proxy terminates the client's connection and opens a new one to the destination. A NAT gateway rewrites addresses and ports on packets that pass through it without ever becoming an endpoint. A VPN encapsulates whole IP packets inside an encrypted tunnel and delivers them into another network, again without terminating the inner connection. A load balancer is a distribution policy, not a layer: it is a proxy when it terminates connections and it is a packet forwarder when it does not. Those four descriptions are the whole distinction, and every practical difference (what the origin sees, what breaks, what can be logged) follows from them.

The one test that separates them#

Ask: can the box answer in the protocol's own language?

A reverse proxy can return 502 Bad Gateway because it holds a live HTTP connection to the client and can compose a response. A NAT gateway cannot; if the destination is unreachable, the best it can do is emit an ICMP error or drop the packet, and the client's own TCP stack reports the failure. A VPN concentrator is in the same position: it moves packets, so the failure the application sees is a timeout or a connection reset originated by the real endpoint.

The corollary is diagnostic: an HTML error page, a 503, or an unexpected TLS certificate means something on the path terminated. A bare Connection timed out or Connection reset by peer means the failure came from an endpoint and the middleboxes were forwarding.

Full comparison#

Forward/reverse proxy (L7)L4 proxy / LB in proxy modeLB in DSR or NAT modeNAT gatewayRouted VPN
Layer743 to 43 to 43 (tunnelled)
Terminates connectionsYes, two independent onesYes, two independent TCP sessionsNo, one TCP session end to endNoNo, the inner connection is untouched
What is rewrittenThe whole message: request line, headers, sometimes bodyNothing in the payload; new IP/TCP headers on the upstream sideDestination MAC (DSR) or destination IP/port (NAT mode)Source IP and port, plus checksumsNothing inner; an outer IP/UDP/ESP header is added
Client IP at the originThe proxy's egress IPThe proxy's egress IPThe real client IPThe NAT device's public IPThe VPN's assigned inner IP, or the exit NAT's IP
How the real client IP is recoveredX-Forwarded-For / Forwarded headersPROXY protocol header, or IP_TRANSPARENT spoofingNot neededNot recoverable without external correlationNot needed inside the tunnel
Origin can observeFull request content, the proxy's TLS stack, the proxy's TCP fingerprintCiphertext, the proxy's TCP fingerprintThe client's real TCP fingerprint and TLS stackThe client's TCP fingerprint, the NAT's addressThe client's real stack, from the inner address
IP TTL seen at the originReset by the proxy's own stackResetDecremented once per hopDecrementedInner TTL decremented once by the tunnel
Can enforce content policyYesOnly on SNI/ALPNNoNoNo
Retries a failed backendYes, per requestOnly before bytes are relayedNoNoNo
Typical softwarenginx, HAProxy mode http, Envoy, Squid, CaddyHAProxy mode tcp, nginx stream, Envoy TCP proxyIPVS in DR mode, ECMP/Maglev forwardersLinux MASQUERADE, cloud NAT gateway, CGNATWireGuard, IPsec, OpenVPN
Signature failure502, 504, header rewriting bugsHalf-open connections, pool exhaustionAsymmetric routing, health-check blind spotsPort exhaustion, no inbound reachabilityMTU/PMTU black holes, split-tunnel DNS leaks

Why a NAT gateway is not a proxy#

Network address translation (RFC 3022) maintains a table mapping (inside IP, inside port) to (outside IP, outside port) and rewrites the relevant fields on each packet, then fixes the IP and TCP/UDP checksums. On Linux this table is nf_conntrack, and you can read it directly:

bash
conntrack -L -p tcp --dport 443 | head -1
text
tcp 6 431999 ESTABLISHED src=10.0.3.17 dst=93.184.216.34 sport=51422 dport=443 \
  src=93.184.216.34 dst=203.0.113.9 sport=443 dport=51422 [ASSURED] mark=0 use=1

That is one connection described from two vantage points. The TCP sequence numbers, window scaling, timestamps and the TLS handshake are all the client's; the gateway only edited addressing fields. There is no second socket, no buffer, no timeout of its own beyond the conntrack idle expiry (the Linux default for established TCP is 432000 seconds, which is where the 431999 above comes from).

Compare a proxy handling the same request. On the proxy host there are two sockets:

bash
ss -tnp state established '( sport = :8080 or dport = :443 )'
text
Recv-Q Send-Q      Local Address:Port       Peer Address:Port
0      0             10.0.1.5:8080          10.0.3.17:51422   users:(("nginx",pid=812,fd=13))
0      0             10.0.1.5:44118       93.184.216.34:443   users:(("nginx",pid=812,fd=14))

Two file descriptors, two sequence-number spaces, two independent congestion windows. That is the structural difference, and it explains the behavioural ones: the proxy can hold the client waiting while it retries a second upstream, can return an error page, can rewrite headers, and can be the bottleneck. NAT can do none of those things and also cannot be the bottleneck in the same way, since its per-flow cost is a hash table entry.

Why an L4 load balancer is sometimes a proxy#

"L4 load balancer" covers two implementations that behave completely differently at the endpoint.

Proxy mode. HAProxy in mode tcp, nginx's stream module and Envoy's TCP proxy all accept the client's TCP connection, then open a separate connection to a chosen backend and copy bytes. This is a proxy by every criterion: two sockets, its own timeouts, its own connection to the backend. The backend sees the load balancer's IP. To recover the client address you either prepend a PROXY protocol header (send-proxy or send-proxy-v2 in HAProxy) or use transparent-proxy address spoofing (source 0.0.0.0 usesrc clientip, which needs IP_TRANSPARENT and routing that returns the backend's replies through the load balancer). The PROXY protocol decoder is useful when you need to confirm what a v2 header actually contains.

Forwarding mode. IPVS in direct routing (DR) mode rewrites only the destination MAC address; the backend has the virtual IP configured on a loopback alias, answers the client directly, and the load balancer never sees the return traffic. ECMP and Maglev-style forwarders behave similarly. Here the backend sees the true client IP with zero configuration, the load balancer has no per-connection buffers, but it also cannot retry, cannot rewrite, cannot terminate TLS and gets no visibility into responses.

The decision rule follows directly:

RequirementChoose
Need per-request routing, retries, header manipulation, TLS terminationL7 proxy
Need TLS passthrough but multiple backends on one IP:portL4 proxy with SNI routing
Need the real client IP at the backend with no application change, at very high packet ratesForwarding mode (DSR/ECMP)
Need the real client IP and connection-level controlL4 proxy plus PROXY protocol

Cloud load balancers straddle this. An application load balancer always terminates and appends X-Forwarded-For. A network load balancer's behaviour depends on a client-IP-preservation attribute on the target group, so check the attribute rather than assuming; when preservation is off, the PROXY protocol option is the supported way to carry the address.

Where a VPN sits#

A VPN is an encapsulation, not an intermediation. WireGuard, IPsec ESP and OpenVPN take an entire IP packet, encrypt it, and put it inside a new outer packet addressed to the tunnel peer. The inner TCP connection runs unchanged from the client's stack to the destination's stack. That has three consequences worth internalising:

  1. It is protocol-agnostic and application-blind. A VPN carries DNS, ICMP, SMB and QUIC as happily as HTTP, and cannot make decisions about any of them, because it never parses them. A proxy is the opposite: protocol-aware, therefore selective.
  2. MTU becomes your problem. The outer header eats payload space. wg-quick sets the interface MTU to the underlying device's MTU minus 80 bytes, which is why 1420 is the familiar WireGuard number on a 1500-byte path. If Path MTU Discovery is broken by an ICMP-filtering firewall, you get the classic signature: handshakes succeed, small requests work, and any response larger than roughly one segment hangs forever. TCP MSS clamping (iptables -t mangle -A FORWARD -p tcp --syn -j TCPMSS --clamp-mss-to-pmtu) is the standard mitigation.
  3. The IP the origin sees depends on what is at the tunnel exit, not on the VPN itself. Full-tunnel VPNs almost always NAT at the exit, so the origin sees the exit gateway's address. This is why "a VPN hides my IP" and "a proxy hides my IP" produce the same observable result via completely different mechanisms.

VPNs also scope by route, not by destination name. A split tunnel sends selected prefixes into the tunnel; the analogous proxy control is the no_proxy environment variable, which is per-application and hostname-based, with entirely different matching semantics. Corporate laptops routinely sit inside a split tunnel and point at a proxy, with only one of the two exclusion lists kept current.

Client IP consequences, one by one#

MiddleboxWhat the origin's socket showsWhat to configure
L7 reverse proxyThe proxy's egress IPAppend X-Forwarded-For/Forwarded at the edge, strip inbound copies, define the trust boundary
Chained L7 proxies (CDN then ingress)The last proxy's IPCount hops from the right; the leftmost entry is attacker-controlled
L4 proxyThe proxy's egress IP, and no header exists to fix itPROXY protocol v2, or IP_TRANSPARENT spoofing
DSR load balancerThe real client IPNothing
NAT gatewayThe NAT's public IP, shared by every client behind itNothing recovers it; do not use IP for identity
CGNATOne address shared by thousands of subscribersRate limiting by IP will punish unrelated users
VPN with exit NATThe VPN exit IPSame as NAT

Two rules fall out of this table. First, never treat a source IP as an identity unless you control every hop between the client and the socket. Second, the header chain is only as trustworthy as your trusted-proxy list: an inbound X-Forwarded-For from an untrusted peer is user input. Work a specific chain through the client IP resolver and read client IP spoofing through proxies before writing allow-list logic against it.

Failure modes#

  • Intermittent connection failures behind a NAT gateway. One NAT address offers roughly 64k source ports per destination tuple, and a workload opening many short-lived connections to a single destination exhausts them. Symptom: sporadic connection timed out under load with no server-side log entry. Fix: more egress addresses, connection reuse, or a proxy with an upstream keep-alive pool.
  • Health checks pass but real traffic fails, DSR mode. The probe reaches the backend's real address while clients arrive on the loopback-configured virtual IP, so a missing arp_ignore/arp_announce setting stays invisible to health checking.
  • TLS handshake succeeds, first large response never arrives, over a VPN. PMTU black hole. Confirm with ping -M do -s 1400 across the tunnel and clamp MSS.
  • All users rate-limited together after inserting a proxy. The application is keying limits on the socket peer address, which is now one proxy IP. Fix at the application, using a resolved client IP with an explicit trusted proxy configuration.
  • 502 appears where a timeout used to. Expected after replacing a forwarder with a proxy: the proxy now converts upstream failures into HTTP statuses. See 502 vs 503 vs 504 for reading them.
  • Traceroute stops at the middlebox. A proxy terminates, so a TCP traceroute to the destination port reaches only the proxy. This is a useful positive signal rather than a fault.

Frequently asked questions#

Is a VPN just a proxy that encrypts everything?#

No. A VPN encapsulates IP packets and routes them, leaving the inner TCP or QUIC connection intact from the client's stack to the destination's. A proxy terminates the connection and creates a new one, so it can see, alter and refuse individual requests. The practical difference is scope and control: a VPN applies to whatever the routing table sends into it, regardless of protocol, while a proxy applies per application and per protocol and can enforce content policy.

Does NAT hide my IP address the way a proxy does?#

The origin sees the NAT device's public address instead of your private one, so superficially yes. But NAT preserves your TCP and TLS characteristics exactly, offers no application-layer control, and shares one address across everyone behind it. It is address multiplexing, not intermediation, and it is not a privacy mechanism.

Is a reverse proxy a load balancer?#

A reverse proxy becomes a load balancer as soon as it distributes across more than one upstream, which nginx, HAProxy, Envoy, Caddy and Traefik all do. The terms describe different aspects of the same box: "reverse proxy" describes the role relative to the origin, "load balancer" describes the selection policy across backends. A load balancer that forwards packets without terminating is not a reverse proxy.

Why does my backend see the load balancer's IP with one product and the client's IP with another?#

Because they use different forwarding models. Terminating products (application load balancers, HAProxy in mode tcp or mode http) open a new connection from their own address. Forwarding products (direct server return, ECMP-based designs, and network load balancers with client IP preservation enabled) leave the source address intact. Check the specific attribute rather than the product category, since the same product can do both depending on configuration.

Can I put a proxy and a VPN in the same path?#

Yes, and it is a common corporate arrangement: a split-tunnel VPN routes internal prefixes, and an explicit proxy handles internet-bound HTTP. The failure to plan for is that the two use unrelated exclusion mechanisms, VPN routes versus no_proxy hostname matching, so a host can be reachable by routing yet still sent to a proxy that cannot reach it. Verify the hostname list with the no_proxy tester rather than reasoning about it.

Which one preserves end-to-end TLS?#

VPNs, NAT and forwarding-mode load balancers all preserve end-to-end TLS, because the TLS session is negotiated between the client and the origin. L4 proxies preserve it too when configured for passthrough, since they relay ciphertext. L7 proxies do not: they terminate TLS, and any encryption to the upstream is a second, separate session, which is the distinction covered in TLS termination, passthrough and re-encryption.

Primary sources#

Every normative claim on this page is checked against the specification or the vendor documentation listed here. Where behaviour is version dependent, the version is named in the text.

  1. RFC 3022: Traditional IP Network Address Translator (Traditional NAT)
  2. RFC 6888: Common Requirements for Carrier-Grade NATs (CGNs)
  3. RFC 4301: Security Architecture for the Internet Protocol
  4. RFC 9110: HTTP Semantics, section 3.7 Intermediaries
  5. The PROXY protocol specification
  6. Linux Virtual Server: how virtual server works
  7. HAProxy configuration manual: mode, source, send-proxy
  8. WireGuard: next generation kernel network tunnel

Found something wrong, or behaviour that differs on your version? Report it with the version number and a primary source. Anything substantive is fixed in the page and logged on the corrections page. See editorial standards for how pages are researched, sourced and reviewed.

More in proxy fundamentals#