Summary
When using the official Cloudflare Workers proxy pattern with Rybbit Cloud, pageviews from a German visitor are attributed to the United States.
The browser is seen by Cloudflare as Germany, and the Worker forwards the original visitor IP via X-Forwarded-For, but Rybbit appears to geolocate Cloudflare's synthetic Worker subrequest IP (2a06:98c0:3600::103) instead of the forwarded visitor IP.
This seems specific to the topology visitor -> Cloudflare Worker on customer zone -> app.rybbit.io, where app.rybbit.io is also behind Cloudflare.
Related: #444
Setup
- Rybbit Cloud:
https://app.rybbit.io
- Website served from Cloudflare Workers static assets
- Tracker loaded as first-party script:
<script src="/analytics/script.js" data-site-id="..." defer></script>
- Worker proxy maps
/analytics/* to https://app.rybbit.io/api/*
- Worker sets
X-Forwarded-For and X-Real-IP to the incoming CF-Connecting-IP
- Worker does not forward cookies or auth headers
This follows the same core pattern as the Rybbit Cloudflare Workers proxy guide:
https://github.com/rybbit-io/rybbit/blob/master/docs/content/docs/(docs)/proxy-guide/cloudflare-workers.mdx
The Astro proxy guide is also relevant for the first-party proxy model:
https://github.com/rybbit-io/rybbit/blob/master/docs/content/docs/(docs)/proxy-guide/astro.mdx
What I observed
From the same Safari session that produced the Rybbit visit, Cloudflare reports Germany:
https://www.cloudflare.com/cdn-cgi/trace
colo=DUS
loc=DE
warp=off
gateway=off
rbi=off
But the Rybbit dashboard shows the session country as United States.
I also checked the Rybbit browser script behavior. Loading /analytics/script.js causes the script to derive the analytics host from its own script URL, so events are posted to /analytics/track. They are not bypassing the proxy and going directly from the browser to https://app.rybbit.io/api/track.
Edge behavior
Using wrangler dev --remote with a minimal Worker that fetches https://www.cloudflare.com/cdn-cgi/trace while setting X-Forwarded-For and X-Real-IP to the visitor IP, the target Cloudflare zone still sees:
ip=2a06:98c0:3600::103
colo=DUS
loc=DE
This matches Cloudflare's documented cross-zone Worker behavior:
https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-connecting-ip
Cloudflare documents that cross-zone Worker subrequests set CF-Connecting-IP to 2a06:98c0:3600::103 for security. Public GeoIP data maps that synthetic IP to the United States, which matches the Rybbit dashboard symptom.
Why this looks like an ingest-side issue
Current master has resolveClientIp() with ASN-aware proxy detection:
https://github.com/rybbit-io/rybbit/blob/master/server/src/services/tracker/resolveClientIp.ts
If CF-Connecting-IP is classified as a datacenter or proxy edge, Rybbit should use X-Real-IP or the first X-Forwarded-For entry. However, the code also documents a fallback where, if the ASN DB is unavailable or the edge IP is not classified, Rybbit treats the request as direct and CF-Connecting-IP wins. In this topology that means geolocating 2a06:98c0:3600::103.
Cloudflare's synthetic Worker IP currently resolves to AS132892. That ASN appears in the generated datacenter ASN list, so this should work if the ASN DB is available and the resolver running in Rybbit Cloud is current.
Expected behavior
For first-party Cloudflare Workers proxy setups following the Rybbit docs, Rybbit Cloud should attribute location to the original visitor from the forwarded visitor IP, not to Cloudflare's synthetic Worker subrequest IP.
In practice, that means trusting the first X-Forwarded-For entry when the request is known to have come through a Cloudflare Worker proxy.
Possible fixes
- Special-case
CF-Connecting-IP: 2a06:98c0:3600::103 as a proxied edge and trust X-Forwarded-For.
- Special-case requests with
CF-Worker present as proxied when X-Forwarded-For is present.
- Make the ASN DB missing or unknown fallback configurable for Rybbit Cloud so documented first-party proxy setups do not silently regress to the proxy IP.
Summary
When using the official Cloudflare Workers proxy pattern with Rybbit Cloud, pageviews from a German visitor are attributed to the United States.
The browser is seen by Cloudflare as Germany, and the Worker forwards the original visitor IP via
X-Forwarded-For, but Rybbit appears to geolocate Cloudflare's synthetic Worker subrequest IP (2a06:98c0:3600::103) instead of the forwarded visitor IP.This seems specific to the topology
visitor -> Cloudflare Worker on customer zone -> app.rybbit.io, whereapp.rybbit.iois also behind Cloudflare.Related: #444
Setup
https://app.rybbit.io/analytics/*tohttps://app.rybbit.io/api/*X-Forwarded-ForandX-Real-IPto the incomingCF-Connecting-IPThis follows the same core pattern as the Rybbit Cloudflare Workers proxy guide:
https://github.com/rybbit-io/rybbit/blob/master/docs/content/docs/(docs)/proxy-guide/cloudflare-workers.mdx
The Astro proxy guide is also relevant for the first-party proxy model:
https://github.com/rybbit-io/rybbit/blob/master/docs/content/docs/(docs)/proxy-guide/astro.mdx
What I observed
From the same Safari session that produced the Rybbit visit, Cloudflare reports Germany:
But the Rybbit dashboard shows the session country as United States.
I also checked the Rybbit browser script behavior. Loading
/analytics/script.jscauses the script to derive the analytics host from its own script URL, so events are posted to/analytics/track. They are not bypassing the proxy and going directly from the browser tohttps://app.rybbit.io/api/track.Edge behavior
Using
wrangler dev --remotewith a minimal Worker that fetcheshttps://www.cloudflare.com/cdn-cgi/tracewhile settingX-Forwarded-ForandX-Real-IPto the visitor IP, the target Cloudflare zone still sees:This matches Cloudflare's documented cross-zone Worker behavior:
https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-connecting-ip
Cloudflare documents that cross-zone Worker subrequests set
CF-Connecting-IPto2a06:98c0:3600::103for security. Public GeoIP data maps that synthetic IP to the United States, which matches the Rybbit dashboard symptom.Why this looks like an ingest-side issue
Current
masterhasresolveClientIp()with ASN-aware proxy detection:https://github.com/rybbit-io/rybbit/blob/master/server/src/services/tracker/resolveClientIp.ts
If
CF-Connecting-IPis classified as a datacenter or proxy edge, Rybbit should useX-Real-IPor the firstX-Forwarded-Forentry. However, the code also documents a fallback where, if the ASN DB is unavailable or the edge IP is not classified, Rybbit treats the request as direct andCF-Connecting-IPwins. In this topology that means geolocating2a06:98c0:3600::103.Cloudflare's synthetic Worker IP currently resolves to AS132892. That ASN appears in the generated datacenter ASN list, so this should work if the ASN DB is available and the resolver running in Rybbit Cloud is current.
Expected behavior
For first-party Cloudflare Workers proxy setups following the Rybbit docs, Rybbit Cloud should attribute location to the original visitor from the forwarded visitor IP, not to Cloudflare's synthetic Worker subrequest IP.
In practice, that means trusting the first
X-Forwarded-Forentry when the request is known to have come through a Cloudflare Worker proxy.Possible fixes
CF-Connecting-IP: 2a06:98c0:3600::103as a proxied edge and trustX-Forwarded-For.CF-Workerpresent as proxied whenX-Forwarded-Foris present.