Forward proxy vs reverse proxy
The difference is who the proxy acts for and who knows it exists. Includes the absolute-form vs origin-form request line, auth, TLS and logging differences.
Key points
- A forward proxy acts for the client and is configured by the client; a reverse proxy acts for the origin and is configured by the server operator.
- The wire discriminator is the request-target:
GET http://example.com/ HTTP/1.1to a forward proxy,GET / HTTP/1.1withHost:to a reverse proxy. - Auth differs by design: forward proxies use
407withProxy-Authenticate, reverse proxies use401withWWW-Authenticate. - Egress gateways and service mesh sidecars are genuinely both, which is why "direction" is a bad way to define the terms.
A forward proxy acts on behalf of the client, is configured by the client, and can reach an open-ended set of destinations. A reverse proxy acts on behalf of the origin server, is configured by the server operator, serves a fixed set of backends, and the client usually does not know it exists. Both sit between a client and a server and both terminate connections, so "direction of traffic" does not distinguish them; agency and configuration ownership do.
The single clearest technical discriminator is on the wire, in the request line.
The request-target tells you which one you are looking at#
RFC 9112 section 3.2 defines four request-target forms. Two of them matter here:
GET http://example.com/index.html HTTP/1.1
Host: example.com
User-Agent: curl/8.5.0That is absolute-form. A client sends it only when it knows it is talking to a proxy. RFC 9112 requires the absolute-form when making a request to a proxy, other than CONNECT or a server-wide OPTIONS.
GET /index.html HTTP/1.1
Host: example.com
User-Agent: curl/8.5.0That is origin-form, the normal case: the path in the request line and the authority in Host. It is what every client sends to an origin server and therefore what a reverse proxy receives.
The spec also settles precedence when both are present. A proxy receiving absolute-form must ignore the received Host field and replace it with the authority from the request-target. Origin servers must accept absolute-form too, and derive the target from it rather than from Host.
For CONNECT the client uses authority-form (CONNECT example.com:443 HTTP/1.1), which only ever appears on a forward proxy connection. See HTTP CONNECT tunnelling.
HTTP/2 erases the wire discriminator#
In HTTP/2 and HTTP/3 there is no request line. Every request carries :scheme, :authority and :path pseudo-headers, so every request is effectively in absolute-form. You cannot tell a forward-proxied request from a direct one by shape alone; you can only tell by who the connection was opened to. This is a real practical consequence: HTTP/2 to an explicit forward proxy is not a simple translation of the HTTP/1.1 case, and support for it in clients and proxies is uneven. Most clients still fall back to HTTP/1.1 with CONNECT for a forward proxy hop, and negotiate HTTP/2 end to end inside the tunnel.
Full comparison#
| Dimension | Forward proxy | Reverse proxy |
|---|---|---|
| Acts on behalf of | The client | The origin server |
| Configured by | The client or its administrator (http_proxy, browser settings, PAC/WPAD, MDM policy) | The service operator, in the proxy's own config or a control plane |
| Client awareness | Aware (explicit) or deliberately unaware (intercepting) | Normally unaware; the proxy is the origin as far as the client is concerned |
| Destination set | Open-ended, any host the policy allows | Closed, a defined list of upstreams |
| Request-target on the wire | Absolute-form for plaintext HTTP, authority-form for CONNECT | Origin-form plus Host |
| DNS resolution | Usually done by the proxy; the client may never resolve the name | Done by the client for the public name, then by the proxy for the upstream |
| TLS | Normally not terminated: CONNECT tunnels ciphertext. Terminating requires an installed private root CA | Normally terminated at the proxy; it holds the site's certificate and key |
| Certificate presented to the client | None, unless intercepting TLS | The origin's public certificate |
| Authentication | 407 Proxy Authentication Required with Proxy-Authenticate/Proxy-Authorization (hop-by-hop) | 401 Unauthorized with WWW-Authenticate, or application-level sessions and tokens |
| Header it adds | Via, sometimes X-Forwarded-For; often strips identifying headers on purpose | X-Forwarded-For, X-Forwarded-Proto, X-Real-IP, Forwarded, Via |
| Logging perspective | Per user or per device: who went where, for policy and audit | Per site or per route: what was requested, status, latency, upstream |
| Caching intent | Shared client-side cache, saves egress bandwidth | Gateway cache, offloads the origin |
| Failure blamed on | The client's network or proxy policy (407, 403 Forbidden from Squid http_access) | The service (502, 503, 504) |
| Scaling driver | Number of users and concurrent tunnels | Request rate and origin capacity |
Configuration: two different mental models#
A forward proxy's configuration is policy about clients. Squid's structure is representative: define who may use it, then what they may reach.
acl corpnet src 10.0.0.0/8
acl SSL_ports port 443
acl CONNECT method CONNECT
http_access deny !corpnet
http_access deny CONNECT !SSL_ports
http_access allow corpnet
http_port 3128A reverse proxy's configuration is routing about services. It maps an inbound identity (hostname, path, SNI) to an upstream:
server {
listen 443 ssl;
server_name api.example.com;
location /v1/ {
proxy_pass http://api_backend/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}The trailing slash on proxy_pass here is load-bearing: with it, /v1/orders becomes /orders upstream; without it, /v1/orders stays /v1/orders. That behaviour trips people up constantly, and is worked through in nginx proxy_pass and the trailing slash with an interactive proxy_pass URI simulator.
Note what is absent from each. The forward proxy config has no upstream list, because the upstream is whatever the client asked for. The reverse proxy config has no client identity rules, because it serves the public.
Worked example: the same host, both ways#
Run a request through an explicit forward proxy and watch the request line:
curl -v -x http://proxy.corp:3128 http://example.com/index.html> GET http://example.com/index.html HTTP/1.1
> Host: example.com
> User-Agent: curl/8.5.0
> Accept: */*
> Proxy-Connection: Keep-AliveNow the same URL over HTTPS through the same proxy:
curl -v -x http://proxy.corp:3128 https://example.com/index.html> CONNECT example.com:443 HTTP/1.1
> Host: example.com:443
< HTTP/1.1 200 Connection established
> GET /index.html HTTP/1.1
> Host: example.comThe inner request is in origin-form, because inside the tunnel curl is talking to the origin directly. The proxy's log has one line for the whole session: CONNECT example.com:443, plus byte counts. That asymmetry is why forward-proxy URL filtering degrades to hostname filtering for HTTPS. More curl-through-proxy debugging patterns are in curl through a proxy.
DNS resolution differs, and it changes what breaks#
With -x http://proxy:3128, curl does not resolve example.com: it connects to the proxy and hands over the name. With SOCKS the choice is explicit, and curl exposes it as two options: --socks5 resolves the hostname locally and sends an address, --socks5-hostname sends the name for the proxy to resolve. Split-horizon DNS makes this a real bug source: local resolution returns a public address for an internal name, and the connection lands on the wrong side of the network. A reverse proxy never has this problem, because the client resolves only the public name.
Authentication and identity#
The two roles authenticate different parties, and HTTP gives them separate status codes and header pairs precisely so both can coexist on one request.
- Forward proxy:
407 Proxy Authentication Required,Proxy-Authenticate: Basic realm="corp", client answers withProxy-Authorization. These are hop-by-hop and must not be forwarded onward. - Reverse proxy or origin:
401 Unauthorized,WWW-Authenticate, client answers withAuthorization. End-to-end.
A request can legitimately carry both headers at once: Proxy-Authorization for the corporate gateway, Authorization for the API. Corporate deployments frequently use Kerberos or NTLM at the proxy, which are connection-oriented rather than request-oriented and therefore break clients that assume every request is independent. That whole area, including why 407 loops happen in CLI tools, is in proxy authentication.
Logging: same event, two different records#
For one user fetching one page, a forward proxy logs the subject and a reverse proxy logs the object.
| Field | Forward proxy log | Reverse proxy log |
|---|---|---|
| Identity | Authenticated username or source IP of the employee device | Client IP, usually reconstructed from X-Forwarded-For |
| Target | Full URL for plaintext, host:port only for CONNECT | Full path and query, always |
| Status | Proxy's own decision plus the origin's status | Upstream status plus the proxy's own rewrite of it |
| Useful for | Acceptable-use policy, data-loss investigation, egress inventory | SLO measurement, error budgets, per-route latency |
The practical consequence: forward proxy logs are the only place an organisation can see outbound dependencies, which is why they are the first artefact requested when auditing what a fleet talks to. Reverse proxy logs cannot answer that question at all.
The cases that are genuinely both#
Some deployments do not fit the split, and forcing them into it causes bad configuration.
- Egress gateways. An internal service is configured to send outbound traffic to a gateway (forward proxy behaviour: the client is configured, destinations are open-ended), but the gateway is operated by the platform team and applies allow-lists, TLS origination and audit logging (reverse proxy behaviour: operator-owned policy, fixed identity). Istio's egress gateway is a reverse proxy binary deployed in a forward proxy role.
- Service mesh sidecars. A single Envoy process runs both an outbound listener that intercepts the application's egress (forward proxy, transparent, via iptables redirection) and an inbound listener that fronts the local application (reverse proxy). The same process holds both mTLS roles: client identity outbound, server identity inbound. See Envoy listeners, routes and clusters.
- A reverse proxy misconfigured into an open forward proxy. The classic nginx footgun is
proxy_pass $scheme://$host$request_uri;with aresolverconfigured and no host restriction. That turns a public reverse proxy into an unauthenticated forward proxy for the whole internet, which is abused for spam relaying and for reaching internal metadata endpoints. This is covered in open proxies and misconfigured relays and overlaps with SSRF at the proxy layer.
The decision rule that resolves ambiguous cases: ask who has to change configuration when the destination set changes. If clients must be reconfigured, it is a forward proxy. If only the proxy operator touches anything, it is a reverse proxy.
Failure modes#
400 Bad Requestfrom a reverse proxy when a client is behind a forward proxy. The client sent absolute-form to something expecting origin-form, usually because the proxy environment variable was set but the request was meant to be direct. Fix withno_proxy; verify with the no_proxy tester, since the matching rules differ per implementation.- Squid returns
The requested URL could not be retrievedwithERR_INVALID_REQ. An origin-form request arrived on an explicithttp_port. Either the client is not proxy-aware, or the port should be declaredintercept. 407returned repeatedly and the client gives up. The proxy uses a connection-bound scheme (NTLM/Negotiate) and the client opened a fresh connection per request, or an intermediate stripsProxy-Authorizationbecause it is hop-by-hop.- Reverse proxy sends the wrong
Hostupstream. With nginx,proxy_pass http://backend;sendsHost: backendby default becauseproxy_set_header Host $proxy_host;is the default. Virtual-hosted upstreams then return the wrong site or a404. X-Forwarded-Forcontains a client-supplied value. A forward proxy may add the internal client IP; if your reverse proxy appends rather than replaces and trusts the whole chain, the attacker controls the leftmost entry. Pin the trust boundary as described in configuring trusted proxies.
Frequently asked questions#
Is nginx a forward proxy or a reverse proxy?#
nginx is a reverse proxy by design and is used that way almost universally. It can be coerced into forward proxy behaviour for plaintext HTTP with a variable-based proxy_pass, and can handle CONNECT only with a third-party module, because the core has no CONNECT support. Using it as a forward proxy is not recommended: the configurations that achieve it are the same ones that create open relays.
Can one server be both a forward and a reverse proxy?#
Yes, and that is normal in service meshes and egress gateways. A single Envoy or HAProxy process can bind one listener that fronts local applications (reverse) and another that handles outbound traffic from those applications (forward). Keep them on separate listeners with separate access rules, because the trust assumptions are opposite: the reverse listener treats input as hostile, the forward listener treats the client as trusted but the destination as unknown.
Does a reverse proxy hide the client or the server?#
A reverse proxy hides the server: the client sees the proxy's address and the origin's identity is not directly reachable. A forward proxy hides the client: the origin sees the proxy's egress address. Neither is a privacy guarantee, since both commonly add forwarding headers that reveal what they hid.
Why does my request line show the full URL in the proxy log?#
Because the client was explicitly configured to use a proxy and therefore sent the absolute-form request-target, per RFC 9112. Seeing full URLs in a proxy access log is proof the traffic was explicitly proxied plaintext HTTP. Intercepted traffic shows origin-form paths reconstructed against the Host header, and HTTPS through the same proxy shows only CONNECT host:443.
Do forward proxies need the Host header at all?#
For plaintext HTTP the Host header is required by HTTP/1.1 but is not authoritative: the proxy must take the authority from the absolute-form request-target and overwrite Host with it before forwarding. Clients send both because HTTP/1.1 mandates Host on every request, not because the proxy needs it.
Which one do I need for outbound API allow-listing?#
A forward proxy, or an egress gateway playing that role. Allow-listing outbound destinations requires an intermediary the client is pointed at, since you are controlling where your own workloads may go. A reverse proxy cannot do it: it only ever sees traffic addressed to services you already run.
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.
- RFC 9110: HTTP Semantics, section 3.7 Intermediaries
- RFC 9112: HTTP/1.1, section 3.2 Request Target
- RFC 9113: HTTP/2, section 8.3.1 Request Pseudo-Header Fields
- RFC 7235: HTTP/1.1 Authentication
- nginx ngx_http_proxy_module
- Squid configuration directive: http_access
- curl manual page: --proxy, --socks5-hostname
- Envoy sidecar: original destination and outbound 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.