The single place in this codebase that knows what cf-connecting-ip,
cf-ray, and request.cf are. Before 1.2.0 those names were read inline by
the security middleware and the request logger, which is what kept two
otherwise platform-agnostic pieces of HTTP plumbing tied to one vendor.
Three of the choices here are security decisions rather than parsing details:
CF-Connecting-IP, never X-Forwarded-For. Cloudflare sets the former
at the edge and overwrites it on every request; the latter is whatever the
caller sent, because Cloudflare appends to it rather than replacing it.
Anything trusting X-Forwarded-For can be told any address it likes —
including by whatever rate limiting or audit trail reads the value back.
CF-Connecting-IPv6 ahead of it. A zone with Pseudo-IPv4 set to
overwrite headers replaces CF-Connecting-IP with a synthetic address in
240.0.0.0/4 for every IPv6 client. It looks like an ordinary address, so
nothing downstream notices — but it is reserved space that routes nowhere,
geolocates to nothing, and need not be stable between requests from one
client. That is worse than logging no address at all, because it reads as a
real answer: an abuse investigation follows it and finds nobody. Reading
the IPv6 header first means the true address wins wherever the zone offers
it, whatever the Pseudo-IPv4 setting happens to be.
CF-Ray as the request id. It is the id Cloudflare's own logs use, so a
line here and a line in the dashboard can be joined. When it is absent — a
local wrangler dev run, a test — a UUID stands in, so correlation degrades
to per-process rather than disappearing.
Why an inbound header wins over CF-Ray
A Service Binding hop is a subrequest the edge never sees, so the callee gets
no CF-Ray of its own and would otherwise mint a UUID — one request, three
Workers, three unrelated ids, and no way to walk a failure back to its cause.
RequestCorrelation.HEADER is what the caller sends to prevent that,
and it is read first precisely so the upstream id is the one the whole
fan-out shares.
The cost is that the value is attacker-controlled at the edge: a public
caller may send any X-Request-Id it likes. That is tolerable because the id
does nothing but join log lines — the worst a forged one achieves is two
unrelated requests appearing under one id in a query. It is not a credential,
nothing authorises against it, and nothing here may start.
Reads Cloudflare's account of a request.
Remarks
The single place in this codebase that knows what
cf-connecting-ip,cf-ray, andrequest.cfare. Before 1.2.0 those names were read inline by the security middleware and the request logger, which is what kept two otherwise platform-agnostic pieces of HTTP plumbing tied to one vendor.Three of the choices here are security decisions rather than parsing details:
CF-Connecting-IP, neverX-Forwarded-For. Cloudflare sets the former at the edge and overwrites it on every request; the latter is whatever the caller sent, because Cloudflare appends to it rather than replacing it. Anything trustingX-Forwarded-Forcan be told any address it likes — including by whatever rate limiting or audit trail reads the value back.CF-Connecting-IPv6ahead of it. A zone with Pseudo-IPv4 set to overwrite headers replacesCF-Connecting-IPwith a synthetic address in240.0.0.0/4for every IPv6 client. It looks like an ordinary address, so nothing downstream notices — but it is reserved space that routes nowhere, geolocates to nothing, and need not be stable between requests from one client. That is worse than logging no address at all, because it reads as a real answer: an abuse investigation follows it and finds nobody. Reading the IPv6 header first means the true address wins wherever the zone offers it, whatever the Pseudo-IPv4 setting happens to be.CF-Rayas the request id. It is the id Cloudflare's own logs use, so a line here and a line in the dashboard can be joined. When it is absent — a localwrangler devrun, a test — a UUID stands in, so correlation degrades to per-process rather than disappearing.Why an inbound header wins over
CF-RayA Service Binding hop is a subrequest the edge never sees, so the callee gets no
CF-Rayof its own and would otherwise mint a UUID — one request, three Workers, three unrelated ids, and no way to walk a failure back to its cause. RequestCorrelation.HEADER is what the caller sends to prevent that, and it is read first precisely so the upstream id is the one the whole fan-out shares.The cost is that the value is attacker-controlled at the edge: a public caller may send any
X-Request-Idit likes. That is tolerable because the id does nothing but join log lines — the worst a forged one achieves is two unrelated requests appearing under one id in a query. It is not a credential, nothing authorises against it, and nothing here may start.Author
Bayu Dwiyan Satria
Version
1.2.1
Since
1.0.0