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.
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
CONNECTtunnel 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:
| Step | What happens | Where it goes wrong |
|---|---|---|
| Accept | Complete 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 |
| Decide | Map the parsed identity (Host, SNI, path, destination address) to an upstream: a route, a cluster, a DNS name, a pool member | No matching route, DNS failure, no healthy upstream |
| Rewrite | Adjust what is safe or required to adjust: strip hop-by-hop headers, add Via / X-Forwarded-For, rewrite the request-target, set the upstream Host | Header loss, duplicated forwarding headers, wrong upstream URI |
| Originate | Open or reuse an upstream connection, replay the request, stream the response back | Connect 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) proxy | L7 (application) proxy | |
|---|---|---|
| Parses | TCP/UDP addressing, optionally the TLS ClientHello | Full HTTP messages, gRPC frames, WebSocket frames |
| Routes on | Destination IP/port, SNI, ALPN | Host, path, method, headers, cookies, body |
| Connection mapping | Usually 1 client connection to 1 upstream connection | Many client requests can be multiplexed over a pooled set of upstream connections |
| Sees payload | No (opaque byte stream) | Yes, after TLS termination |
| Retries | Only before any bytes are forwarded | Per request, with idempotency rules |
| Typical software | HAProxy in mode tcp, nginx stream module, Envoy TCP proxy, AWS NLB | nginx 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:
GET http://example.com/index.html HTTP/1.1
Host: example.comis 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
GET /index.html HTTP/1.1
Host: example.comis 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 position | Can see | Cannot see |
|---|---|---|
| Plaintext HTTP (port 80) | Everything: method, URI, headers, body | Nothing hidden |
| TLS terminating (reverse proxy, TLS offload) | Everything, after decrypting; re-encrypts to upstream if configured | Nothing, 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 duration | Request URIs, headers, bodies, cookies |
| TLS passthrough / SNI routing (L4) | SNI, ALPN, cipher list, sizes, timing | Everything else |
| TLS interception with a private CA | Everything, by presenting a forged leaf certificate the client trusts | Nothing, 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#
| Type | Who configures it | What it sees | Typical software | Typical failure |
|---|---|---|---|---|
| Forward proxy | The client (env vars, browser settings, PAC) | Absolute-form URIs for plaintext HTTP; host:port only for CONNECT | Squid, Privoxy, tinyproxy, cloud secure web gateways | 407 loops, no_proxy not matching, PAC returning the wrong node |
| Reverse proxy | The origin operator | Full request after TLS termination | nginx, HAProxy, Envoy, Caddy, Traefik | 502 on upstream refusal, 504 on upstream timeout, wrong Host sent upstream |
| Transparent / intercepting proxy | The network operator; the client is unaware | Origin-form requests plus the original destination IP | Squid intercept/tproxy, WCCP-attached caches, inline appliances | TLS errors on port 443, non-HTTP protocols on port 80 breaking, client IP lost |
| SOCKS proxy | The client, per application | Destination address or hostname and port; payload is opaque | Dante, ssh -D, Shadowsocks, OpenSSH dynamic forward | Local vs remote DNS confusion (socks5 vs socks5h), UDP associate unsupported |
CONNECT tunnel | The client, via an HTTP forward proxy | The CONNECT authority, then ciphertext | Any HTTP proxy; used by every HTTPS-capable client | Proxy denies non-443 ports, tunnel idle timeout kills long-lived streams |
| API gateway | The API owner | Full request plus auth tokens, schema, rate-limit keys | Kong, Apigee, AWS API Gateway, Envoy-based gateways | Token validation adds latency, per-route timeouts shorter than the backend |
| Service mesh sidecar | The platform team, injected automatically | Both directions: outbound as a forward proxy, inbound as a reverse proxy | Envoy (Istio), Linkerd2-proxy | mTLS 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:
CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
Proxy-Connection: Keep-AliveThe 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:
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/Forwardedat the edge, strip client-supplied copies, and configure trusted proxies. 502 Bad Gatewayimmediately, with no upstream log entry. The originate step failed: connection refused, TLS verification failure to the upstream, or nginx'supstream sent too big header while reading response header from upstreamwhen the response headers exceedproxy_buffer_size.504 Gateway Time-outat 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 onby default; server-sent events and long-poll endpoints need it disabled per location. See proxy buffering and streaming responses. - WebSocket upgrade returns
400or falls back to polling.UpgradeandConnectionare 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.
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.