Trust Forwarded Headers Only from Known Proxies

A reverse proxy often knows facts that an application server cannot observe directly. It may terminate TLS, accept the public hostname, and receive the client connection before opening a separate connection to the backend. Forwarding fields carry those facts across the second hop.

That arrangement is safe only when the backend can distinguish proxy-supplied metadata from client-supplied HTTP fields. A header name does not create trust. The trust comes from the network path, the proxy configuration, and a rule that defines which hop may set or replace each field.

The backend sees a different connection

Consider a public request that reaches a TLS-terminating proxy:

browser
  |
  | HTTPS to https://account.example
  v
reverse proxy
  |
  | HTTP to 10.0.0.20:8080
  v
application

At the application socket, the peer may be the proxy, the transport may be plain HTTP, and the local address may be private. Yet application logic may still need the original scheme, host, and client address.

Deployments commonly convey this metadata with the standardized Forwarded field or with fields such as X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto. The exact convention matters less than the trust rule around it.

If the application accepts these fields from every requester, an external client can attempt to supply the same metadata. The backend then has two conflicting sources: properties of the connection it can verify and text inside the request that a client may control.

A forwarding field is an assertion

X-Forwarded-Proto: https is an assertion that an earlier trusted hop received the request over HTTPS. X-Forwarded-Host: account.example is an assertion about the authority seen on that hop. A client-address field is an assertion about a prior network peer.

Assertions need provenance. The backend should accept them only when the immediate peer is a proxy that is authorized to make those assertions.

A common boundary is:

untrusted client
     |
     v
edge proxy  -- replaces forwarding fields -->
     |
     v
application -- trusts fields only from edge proxy

The word replaces is important. If the edge merely preserves arbitrary inbound forwarding fields, client-controlled values can survive into a channel the application treats as trusted. The edge should remove or overwrite fields for which it is the authority, then construct the value according to the deployment’s policy.

Host data can affect generated URLs

Applications sometimes build absolute URLs for password reset messages, OAuth redirects, canonical links, or redirects after login. If public origin data comes from an untrusted forwarded host or scheme, the generated URL can point at an attacker-selected origin even though the request reached the legitimate application.

The safer pattern is to keep the public origin in trusted configuration when it is fixed:

PUBLIC_ORIGIN=https://account.example

When a service legitimately supports multiple public hosts, the accepted host set should still be constrained. Proxy metadata can select among configured hosts, but arbitrary input should not become an authority merely because it arrived in a forwarding field.

This also separates routing from security policy. A reverse proxy may route many hostnames, while a particular application may be valid on only a subset of them.

Client addresses require hop-aware parsing

Client IP handling is more subtle because forwarding fields can contain a chain. A proxy can append an address to an existing list, and multiple trusted proxies can create several entries.

A backend should not assume that the first textual address is always the client. It needs a model of the proxy path. One practical method is to walk the chain from the application side, discard addresses that correspond to trusted proxy hops, and stop at the first address outside that trusted set.

client 198.51.100.8
   |
proxy A
   |
proxy B
   |
application

forwarded chain: 198.51.100.8, proxy-A
socket peer: proxy-B

The exact parsing algorithm must match the behavior of the proxies that construct the field. A fixed hop count can be fragile when traffic can reach the application through paths with different numbers of intermediaries. An explicit trusted-proxy set is often easier to audit.

Client addresses should also remain a weak identity signal. Even correctly derived source addresses can represent NAT gateways, carrier networks, enterprise egress, or privacy relays. They can support rate limiting and telemetry, but they should not silently replace stronger authentication.

Scheme affects secure-request decisions

TLS termination creates another split. The backend connection may be HTTP even though the public request was HTTPS. Frameworks often expose a secure flag or reconstructed request URL based on proxy metadata.

If the application trusts an unverified X-Forwarded-Proto, a client may be able to influence logic that branches on the apparent scheme. The impact depends on the application and framework: redirect behavior, absolute URL construction, cookie policy, and middleware can all consult reconstructed request properties.

The control is not to reject proxy metadata altogether. It is to enable proxy trust only for the actual reverse-proxy path and to define the expected scheme values. An application that is never meant to receive direct Internet traffic should also be protected at the network layer so the proxy is the only reachable peer.

Multiple proxies need one coherent contract

A CDN, load balancer, ingress proxy, and service proxy may all exist in one request path:

client
  |
CDN
  |
load balancer
  |
ingress
  |
application

Each hop needs a documented rule for forwarding metadata. Which hop creates the field? Which hops append? Which fields are replaced? Which private addresses are expected? Can any component be bypassed?

Without that contract, adding one proxy can invalidate an application’s old hop-count assumption. A path used for health checks or internal traffic can also differ from the public path and produce surprising reconstruction.

Standardizing on one internal convention reduces ambiguity. The standardized Forwarded field provides syntax for parameters such as for, host, and proto, but standard syntax does not establish authenticity. Trust still depends on who supplied the field.

Network controls and application controls reinforce each other

Application-level trusted-proxy configuration should not carry the entire burden. If the backend is intended to receive traffic only from a load balancer or ingress tier, firewall rules, security groups, private networking, or equivalent controls should enforce that topology.

Network restriction reduces the chance that an attacker can connect directly to the backend and impersonate the proxy by sending the expected headers. Application checks then provide a second boundary for cases such as configuration drift or additional internal callers.

The proxy itself also needs a strict inbound policy. It should normalize the forwarding fields it owns rather than treating client-provided copies as authoritative.

Logs should preserve both peer and derived values

Troubleshooting proxy trust is difficult when logs contain only one reconstructed address or URL. Useful telemetry records the immediate socket peer separately from the derived client address, and records the effective host and scheme used by the application.

That separation helps expose impossible combinations. A request that claims a public forwarding chain but arrives from a peer outside the proxy network deserves different treatment from ordinary proxied traffic.

Avoid logging secrets embedded in URLs or headers. The goal is provenance for routing metadata, not indiscriminate request capture.

Test direct and proxied paths separately

A secure configuration should be tested with requests that arrive through the intended proxy and, where network access permits, with direct requests to the backend.

Tests can verify that:

  • trusted proxy requests produce the expected public host, scheme, and client address;
  • client-supplied forwarding fields are replaced at the edge;
  • direct backend requests cannot inject trusted origin or client metadata;
  • malformed forwarding values are rejected or ignored according to policy;
  • alternate internal paths do not accidentally receive broader trust.

These tests are especially useful after changing a CDN, load balancer, ingress controller, or framework proxy setting. The security property depends on the whole path rather than one component in isolation.

Proxy trust should be narrow and explicit

Forwarding metadata solves a real information gap introduced by reverse proxies. It should be treated as authenticated infrastructure metadata, not ordinary client input.

A sound deployment ties trusted fields to known proxy peers, has those proxies replace the fields they own, constrains host and scheme values, parses address chains according to the real topology, and prevents direct access where the architecture does not require it.

That boundary keeps request reconstruction useful without granting arbitrary HTTP text the authority of the network path.

References