Forwarding the visitor IP
This is the most important concept to understand in Full mode. Get it right and your location data is accurate. Get it wrong and every single visitor appears to be in the same city, your server’s location.
The problem
Snow geolocates each event from the IP address of the request. When you set up a first-party proxy, events no longer come straight from your visitors’ browsers. They come from your server (Nginx, Caddy, a Cloudflare Worker, a Next.js Route Handler, etc.). So the IP Snow sees is your server’s IP.
The result: your Locations report shows ~100% of visitors in one city, wherever your server is.
This is the single most common mistake in full first-party proxy setups.
This only applies to Full mode (proxying both the script and events). In Simple mode, events go directly from the visitor’s browser to Snow, so the real IP arrives automatically. If you can live with Simple mode, the IP problem doesn’t exist.
The fix: tell Snow the real IP
Your proxy knows the real visitor IP. It’s the socket it accepted the connection from. The fix is to forward that IP to Snow in a header alongside the proxied request.
Snow accepts two forwarding methods:
Method 1: X-Forwarded-For (zero-config)
Your proxy sends the standard X-Forwarded-For header with the real client IP. Snow reads it.
X-Forwarded-For: 203.0.113.42This is what most proxies do by default. Nginx uses $remote_addr, Cloudflare Workers read CF-Connecting-IP. No Snow-specific configuration required.
Limitation. X-Forwarded-For can in principle be spoofed by a determined client who sends their own header before your proxy adds its value. Most proxy configurations append to or replace the header from $remote_addr, which is the actual socket IP and not spoofable, so in practice this is fine for most sites.
Method 2: Secure proxy (recommended)
Enable Secure proxy in Settings → Proxy. Snow generates a per-site secret, shown once. Your proxy sends two headers:
X-Snow-Client-IP: 203.0.113.42
X-Snow-Proxy-Secret: your_per_site_secretSnow only trusts X-Snow-Client-IP when X-Snow-Proxy-Secret matches your site’s stored secret. The IP cannot be spoofed by visitors because they don’t know your secret.
This method is unaffected by intermediate proxies or CDNs that might modify X-Forwarded-For in transit.
The secret is shown once when you first enable Secure proxy. Store it somewhere safe, like an environment variable. If you lose it, rotate it in Settings; the old secret stops working immediately and a new one is issued.
The header contract
When proxying the /sa/api/collect route in Full mode, send these headers to Snow:
| Header | Value | Notes |
|---|---|---|
X-Snow-Client-IP | Real visitor IP | Used with Secure proxy. Trusted only when secret matches. |
X-Forwarded-For | Real visitor IP | Standard fallback. Used when no secret is present. |
X-Snow-Proxy-Secret | Your per-site secret | Required for X-Snow-Client-IP to be trusted. |
Do not forward visitor cookies or auth headers upstream to Snow. Strip them in your proxy config.
Where each platform reads the visitor IP
| Platform | Source of real visitor IP |
|---|---|
| Cloudflare Worker | request.headers.get("CF-Connecting-IP") |
| Nginx | $remote_addr |
| Caddy | {remote_host} |
| Apache | %{REMOTE_ADDR}s |
| Next.js Route Handler | request.headers.get("x-forwarded-for") (set by your reverse proxy) |
If your proxy sits behind a load balancer, $remote_addr (or its equivalent) is the load balancer’s IP, not the visitor’s. Your load balancer should be setting X-Forwarded-For to the real client; read from that instead.
Enabling and rotating the secret
- Open Settings → Proxy for the site you want to protect.
- Switch to Full mode and toggle Secure proxy on.
- Copy the secret that appears. It’s shown only once. Save it to an environment variable before dismissing the dialog.
- Configure your proxy to send
X-Snow-Proxy-Secretwith that value on the/sa/api/collectroute.
To rotate the secret: go back to Settings → Proxy, disable and re-enable Secure proxy. A new secret is issued immediately; the old one stops working. Update your proxy’s environment variable before rotating, or there will be a gap where events reach Snow without a valid secret (they’re not dropped, Snow falls back to X-Forwarded-For).
To disable: toggle Secure proxy off. Snow stops requiring the secret and falls back to X-Forwarded-For.
Verify it’s working
After deploying, check Analytics → Locations (see Locations & Devices):
- Broken IP forwarding: nearly all visitors show as one city, your server’s location.
- Working IP forwarding: the country/city distribution matches what you’d expect from your real audience.
A useful sanity check: your other sites that don’t use a proxy should show accurate locations too. Compare them as a control. If the non-proxied sites look right but the proxied site doesn’t, the IP forwarding config is the issue.
For a real-time check, open the Live view and visit your own site. Your dot should land on your actual city, not your server’s.