luminare.infra_edge · v0.2.1
0014. Client address from the loopback connector only instead of X-Forwarded-For or Cloudflare's address ranges
- Status: accepted
- Date: 2026-10-21
Context
Behind the tunnel, every request reaches nginx from the connector (cloudflared) on the same
host, so the address of the connection is always 127.0.0.1. The visitor’s address arrives in
the CF-Connecting-IP header and in X-Forwarded-For, to which Cloudflare appends the address
after any list that the visitor sent. Logs, rate limits and applications need the visitor’s
address, and nobody else may be able to choose it. Applications also need the public scheme
(X-Forwarded-Proto) to build their links.
Considered options
- Trust
CF-Connecting-IPonly from127.0.0.1(set_real_ip_from,real_ip_header,real_ip_recursive off), and pass the checked address to the application asX-Real-IP,X-Forwarded-ForandCF-Connecting-IP; drop aForwardedheader (RFC 7239) from the request. PassX-Forwarded-Protoon only from the same addresses. - Trust
X-Forwarded-Forrecursively from Cloudflare’s published address ranges. - Pass the headers to the application unchanged (
$proxy_add_x_forwarded_for).
Decision
Option 1, with the listeners on loopback addresses only, so that the connector on the host is the only remote client. Option 2 is meant for an origin that Cloudflare’s proxies connect to directly; with the tunnel they never do, and the ranges change over time. Option 3 hands a list that the visitor can choose to the application, and an application that reads its first entry can be given any address.
Consequences
- Good: the Molecule scenario shows that a forged
X-Forwarded-Foris replaced by the checked address and a forgedForwardeddoes not reach the application, and that the sameCF-Connecting-IPfrom127.0.0.2is ignored for the log, for the application and forX-Forwarded-Proto. - Bad: every process on the host that connects from
127.0.0.1is trusted like the connector, so local users can set any address. - Bad: a connector on another host is not supported: it would need a non-loopback listener, which
the role refuses in this version, and its address in
nginx_real_ip_from.
docs/adr/0014-client-address-from-the-loopback-connector-only.md on GitHub
Licensed under Apache-2.0. Source: luminare-dev/ansible-collection-infra_edge at v0.2.1.