Fundamentals

What a proxy server actually does

A proxy terminates one connection and originates another. Covers the accept-decide-rewrite-originate loop, L4 vs L7, TLS visibility and a proxy taxonomy.

· 11 min read · How we verify this

Key points

  • A proxy terminates one connection and originates a second one. A router or NAT device forwards packets and never becomes a connection endpoint.
  • Every proxy does exactly four things: accept, decide, rewrite, originate. Buffering, caching, auth and retries are policy bolted onto that loop.
  • Forward vs reverse is about who the proxy acts for and who configured it, not about the direction traffic travels.
  • A CONNECT tunnel sees hostname, port, SNI, ALPN, byte counts and timing. Under TLS 1.3 it no longer sees the server certificate.

A proxy server is an intermediary that terminates one connection and originates another. A client opens a TCP (or QUIC) connection to the proxy, the proxy reads that connection as a real endpoint, decides where the traffic should go, and then opens a separate connection to the destination and relays data between the two. That termination is the whole definition. A router, a firewall or a NAT gateway rewrites headers on packets that pass through it and never becomes an endpoint; a proxy is an endpoint twice, once as a server and once as a client.

Everything else people call "proxying" is a consequence of that one property: because the proxy is a real endpoint, it owns a socket buffer, a timeout, a TLS session, a connection pool and a log line for each side independently.

The four things every proxy does#

Strip away caching, auth, WAF rules and rate limits and every proxy on the planet runs the same loop:

StepWhat happensWhere it goes wrong
AcceptComplete a TCP/TLS handshake, parse enough of the protocol to know what this is (request line, TLS ClientHello, SOCKS greeting)Malformed framing, oversized headers, handshake timeouts
DecideMap the parsed identity (Host, SNI, path, destination address) to an upstream: a route, a cluster, a DNS name, a pool memberNo matching route, DNS failure, no healthy upstream
RewriteAdjust what is safe or required to adjust: strip hop-by-hop headers, add Via / X-Forwarded-For, rewrite the request-target, set the upstream HostHeader loss, duplicated forwarding headers, wrong upstream URI
OriginateOpen or reuse an upstream connection, replay the request, stream the response backConnect timeouts, pool exhaustion, retry on a non-idempotent request

The useful part of this framing is that it tells you where to look during an incident. A 400-class symptom is almost always accept or rewrite. A 502/503/504 is almost always decide or originate. See 502 vs 503 vs 504 for the mapping from status code to the failing step.

L4 proxies and L7 proxies#

The layer a proxy operates at determines what it can parse, and therefore what it can decide on.

L4 (transport) proxyL7 (application) proxy
ParsesTCP/UDP addressing, optionally the TLS ClientHelloFull HTTP messages, gRPC frames, WebSocket frames
Routes onDestination IP/port, SNI, ALPNHost, path, method, headers, cookies, body
Connection mappingUsually 1 client connection to 1 upstream connectionMany client requests can be multiplexed over a pooled set of upstream connections
Sees payloadNo (opaque byte stream)Yes, after TLS termination
RetriesOnly before any bytes are forwardedPer request, with idempotency rules
Typical softwareHAProxy in mode tcp, nginx stream module, Envoy TCP proxy, AWS NLBnginx http, HAProxy mode http, Envoy, Caddy, Traefik

An L4 proxy that reads the TLS ClientHello to pick a backend is still an L4 proxy: it makes a routing decision from one plaintext field and then relays ciphertext untouched. That is SNI-based routing, and it is the standard way to fan out TLS passthrough traffic to multiple backends on one IP and port.

Explicit versus transparent#

An explicit proxy is one the client was configured to use: http_proxy in the environment, a browser proxy setting, a PAC file, a --proxy flag. The client knowingly speaks proxy protocol semantics, which for HTTP means sending an absolute-URI request-target or a CONNECT request.

A transparent (intercepting) proxy receives traffic the client believed was going straight to the origin, because the network diverted it. The client sends an ordinary origin-form request and the proxy has to reconstruct the destination from the Host header or the original destination address. This is a different code path in every implementation: Squid, for example, needs http_port 3129 intercept rather than a plain http_port 3128, and it will reject explicit-style requests arriving on an intercept port. The mechanics and the breakage are covered in transparent and intercepting proxies.

Forward versus reverse#

A forward proxy acts on behalf of the client and can be pointed at an open-ended set of origins. A reverse proxy acts on behalf of the origin, is configured by the server operator, and serves a fixed set of backends while the client believes it is talking to the origin itself. The clearest wire-level discriminator is the request-target form:

http
GET http://example.com/index.html HTTP/1.1
Host: example.com

is what a client sends to a forward proxy (absolute-form, required by RFC 9112 section 3.2.2 when making a request to a proxy), while

http
GET /index.html HTTP/1.1
Host: example.com

is what it sends to a reverse proxy or an origin server (origin-form). Forward proxy vs reverse proxy works through the configuration, auth and logging consequences.

The state a proxy holds#

"Stateless proxy" is almost always wrong. Even a minimal L7 proxy holds:

  • Per-connection state: socket buffers both sides, parser state (partially received headers), the current request/response phase, and for HTTP/2 the HPACK dynamic table and per-stream flow-control windows.
  • TLS state: negotiated session on the client side, a separate one on the upstream side, plus any session ticket or resumption cache.
  • Upstream pool state: idle keep-alive connections per upstream, in-flight counts used for least-connections balancing, and the results of active health checks. See keep-alive and upstream connection pooling.
  • Decision state: sticky-session mappings, rate-limit counters, cache entries, and the trusted-proxy list used to derive the real client IP.

That state is why proxies are hard to scale horizontally without care: an HTTP/2 connection's HPACK table and a sticky-session cookie mapping both assume the same instance handles the follow-up traffic.

What a proxy can and cannot see#

Visibility is entirely determined by where TLS is terminated.

Proxy positionCan seeCannot see
Plaintext HTTP (port 80)Everything: method, URI, headers, bodyNothing hidden
TLS terminating (reverse proxy, TLS offload)Everything, after decrypting; re-encrypts to upstream if configuredNothing, but it now owns the private key
CONNECT tunnel (forward proxy)Target host and port from the CONNECT line, SNI, ALPN, TLS version, byte counts, timing, connection durationRequest URIs, headers, bodies, cookies
TLS passthrough / SNI routing (L4)SNI, ALPN, cipher list, sizes, timingEverything else
TLS interception with a private CAEverything, by presenting a forged leaf certificate the client trustsNothing, unless the client pins

One detail changes what intercepting middleboxes can log: in TLS 1.2 the server's Certificate message is sent in the clear, so a passive L4 proxy could read the subject and SAN list. In TLS 1.3 (RFC 8446) the certificate is sent after the handshake keys are established and is therefore encrypted. A middlebox on a TLS 1.3 flow has only the ClientHello fields, which is a large part of why SNI became the universal policy hook, and why Encrypted Client Hello is contentious for filtering vendors. If you need to see inside, you are choosing TLS interception with a corporate root CA, with all the trust-store consequences that implies.

A taxonomy of proxy types#

TypeWho configures itWhat it seesTypical softwareTypical failure
Forward proxyThe client (env vars, browser settings, PAC)Absolute-form URIs for plaintext HTTP; host:port only for CONNECTSquid, Privoxy, tinyproxy, cloud secure web gateways407 loops, no_proxy not matching, PAC returning the wrong node
Reverse proxyThe origin operatorFull request after TLS terminationnginx, HAProxy, Envoy, Caddy, Traefik502 on upstream refusal, 504 on upstream timeout, wrong Host sent upstream
Transparent / intercepting proxyThe network operator; the client is unawareOrigin-form requests plus the original destination IPSquid intercept/tproxy, WCCP-attached caches, inline appliancesTLS errors on port 443, non-HTTP protocols on port 80 breaking, client IP lost
SOCKS proxyThe client, per applicationDestination address or hostname and port; payload is opaqueDante, ssh -D, Shadowsocks, OpenSSH dynamic forwardLocal vs remote DNS confusion (socks5 vs socks5h), UDP associate unsupported
CONNECT tunnelThe client, via an HTTP forward proxyThe CONNECT authority, then ciphertextAny HTTP proxy; used by every HTTPS-capable clientProxy denies non-443 ports, tunnel idle timeout kills long-lived streams
API gatewayThe API ownerFull request plus auth tokens, schema, rate-limit keysKong, Apigee, AWS API Gateway, Envoy-based gatewaysToken validation adds latency, per-route timeouts shorter than the backend
Service mesh sidecarThe platform team, injected automaticallyBoth directions: outbound as a forward proxy, inbound as a reverse proxyEnvoy (Istio), Linkerd2-proxymTLS identity mismatch, traffic bypassing the sidecar via unredirected ports

Worked example: the same request through three proxy shapes#

Client asks for https://api.example.com/v1/orders.

Through an explicit forward proxy on proxy.corp:3128, the client sends:

http
CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
Proxy-Connection: Keep-Alive

The proxy replies HTTP/1.1 200 Connection established and then relays bytes. Its access log contains the method CONNECT, the authority, and totals; it never sees /v1/orders. Details in HTTP CONNECT tunnelling.

Through a reverse proxy at api.example.com, the proxy terminates TLS and sees:

http
GET /v1/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJ...

and originates a new request upstream, typically adding X-Forwarded-For and X-Forwarded-Proto because the upstream's socket now shows the proxy's IP, not the client's. That substitution is the single most common source of wrong-IP bugs; see the X-Forwarded-For header and the client IP resolver for working through a specific chain.

Through an L4 SNI router, the proxy reads server_name=api.example.com from the ClientHello, selects a backend, and forwards the untouched ClientHello onward. The backend completes TLS with the real client, so the certificate and the private key stay on the backend, but the backend sees the proxy's IP unless the PROXY protocol is enabled.

Failure modes#

  • Upstream sees the proxy IP everywhere. Symptom: rate limiting fires for all users at once, geolocation resolves to a data centre. Cause: no forwarding header, or the application not configured to trust one. Fix: set X-Forwarded-For/Forwarded at the edge, strip client-supplied copies, and configure trusted proxies.
  • 502 Bad Gateway immediately, with no upstream log entry. The originate step failed: connection refused, TLS verification failure to the upstream, or nginx's upstream sent too big header while reading response header from upstream when the response headers exceed proxy_buffer_size.
  • 504 Gateway Time-out at a suspiciously round number. The proxy hit its own read timeout (60s in nginx by default) before the backend finished. Fix the backend or raise the timeout deliberately, but only after checking the whole ladder.
  • Response arrives all at once instead of streaming. The proxy buffered it. nginx has proxy_buffering on by default; server-sent events and long-poll endpoints need it disabled per location. See proxy buffering and streaming responses.
  • WebSocket upgrade returns 400 or falls back to polling. Upgrade and Connection are hop-by-hop headers, so a conforming proxy drops them unless explicitly told to forward them for that route.

Frequently asked questions#

Is a proxy the same thing as a gateway?#

Not quite. "Gateway" in RFC 9110 is the term for a reverse proxy: an intermediary that acts as the origin server for the inbound connection. All gateways are proxies, but "gateway" carries the extra implication that the client does not know an intermediary exists. In cloud vendor documentation "gateway" is often used loosely for anything at the edge, including NAT gateways, which are not proxies at all.

Does a proxy always change the source IP the server sees?#

Yes, whenever the proxy terminates the connection, because the upstream connection is opened from the proxy's own socket. The original address can only be recovered out of band: an application-layer header such as X-Forwarded-For or Forwarded, the binary PROXY protocol header prepended to the stream, or a kernel-level transparent-proxy setup that spoofs the client address on the upstream socket.

Can a proxy read HTTPS traffic?#

Only if it terminates TLS. A forward proxy handling a CONNECT tunnel sees the target hostname and port, the SNI, the negotiated ALPN, and traffic volume and timing, but not URLs, headers or bodies. To see content, the proxy must present a certificate the client accepts, which in practice means an organisation-managed root CA installed in the client trust store, and it fails against clients that pin certificates.

What is the difference between a proxy and a load balancer?#

Overlapping categories rather than opposites. A load balancer is a proxy if it terminates connections (any L7 load balancer, and L4 load balancers implemented in proxy mode). It is not a proxy if it forwards packets with DSR or NAT-style rewriting without becoming an endpoint. Proxy vs VPN vs NAT vs load balancer draws the line precisely.

Why do proxies strip some headers and not others?#

HTTP distinguishes end-to-end headers, which must survive every hop, from hop-by-hop headers, which describe the single connection and must be consumed by the intermediary. Connection, Keep-Alive, TE, Trailer, Transfer-Encoding, Upgrade, Proxy-Authenticate and Proxy-Authorization are hop-by-hop, plus anything named in the Connection header field. Forwarding them unchanged causes connection-reuse bugs and is one ingredient in request smuggling.

Do proxies work for protocols other than HTTP?#

Yes. SOCKS5 (RFC 1928) proxies arbitrary TCP and, with UDP ASSOCIATE, UDP. L4 proxies relay any TCP stream, which is how database and SMTP proxies work. What you lose below L7 is per-request routing, retries and content inspection, because the proxy has no message boundaries to work with. SOCKS5 vs HTTP proxy compares the two for general-purpose tunnelling.

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 9110: HTTP Semantics
  2. RFC 9112: HTTP/1.1
  3. RFC 1928: SOCKS Protocol Version 5
  4. RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3
  5. RFC 6455: The WebSocket Protocol
  6. nginx ngx_http_proxy_module
  7. Squid configuration directive: http_port
  8. Envoy architecture overview: listeners

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#