Cloudflare reverse proxy
Contents
- If you use a self-hosted proxy, PostHog can't help troubleshoot. Use our managed reverse proxy if you want support.
- Use domains matching your PostHog region:
us.i.posthog.comfor US,eu.i.posthog.comfor EU. - Don't use obvious path names like
/analytics,/tracking,/telemetry, or/posthog. Blockers will catch them. Use something unique to your app instead.
This guide shows you how to use Cloudflare as a reverse proxy for PostHog.
Prerequisites
- A Cloudflare account
- A domain managed by Cloudflare
- For option 2: Cloudflare Enterprise plan
Choose your setup option
Cloudflare sits between your users and PostHog. When a user triggers an event, the request goes to Cloudflare first, then Cloudflare forwards it to PostHog. This hides PostHog's domains from ad blockers.
Cloudflare offers two approaches for proxying. Choose based on your Cloudflare plan and technical preferences:
- Option 1: Cloudflare Workers: Runs serverless JavaScript on Cloudflare's edge network to intercept and forward requests. You write code that handles routing logic, header manipulation, and caching. This gives you full control but requires maintaining code. Works on all Cloudflare plans including free.
- Option 2: DNS and Page Rules: Uses Cloudflare's DNS to route traffic and Page Rules to rewrite headers. Simpler configuration with no code to maintain, but requires an Enterprise plan and has less flexibility.
Option 1: Cloudflare Workers
This method uses Cloudflare's serverless platform to run code that proxies requests to PostHog.
- 1
Create a Cloudflare Worker
Open your Cloudflare dashboard and follow Cloudflare's Workers guide to create a new worker.
Cloudflare Workers are serverless functions that run on Cloudflare's edge network. Your worker will intercept requests to your subdomain and forward them to PostHog with the correct headers.
- 2
Add the proxy code
Replace the default worker code with this:
This code does three things:
- Routes requests: The
handleRequestfunction checks if the request is for static assets (/static/*) or API calls. Static assets go to PostHog's asset server, everything else goes to the main API. - Caches static assets: The
retrieveStaticfunction caches PostHog's JavaScript SDK and other static files in Cloudflare's cache. This improves performance and reduces load on PostHog's servers. - Preserves user location: The
forwardRequestfunction captures the real user IP from Cloudflare'sCF-Connecting-IPheader and sets it asX-Forwarded-For. This ensures PostHog records accurate user locations instead of showing all users at Cloudflare's data center locations. It also removes cookies for privacy.
- Routes requests: The
- 3
Add a custom domain to your worker
In the Cloudflare dashboard, follow Cloudflare's custom domains guide to add a subdomain like
e.yourdomain.comto your worker.Using your own domain instead of the default
*.workers.devdomain makes the proxy less likely to be blocked. Ad blockers recognize and block*.workers.devpatterns.Avoid obvious terms like
tracking,analytics,posthog, ortelemetryin your subdomain name. Use something neutral likee,ph, oringestinstead. - 4
Update your PostHog SDK
In your application code, update your PostHog initialization to use your worker's domain:
Replace
e.yourdomain.comwith your actual subdomain.The
ui_hostmust point to PostHog's actual domain so features like the toolbar link correctly. Verify your setup
CheckpointConfirm events are flowing through your worker:
- Open your browser's developer tools and go to the Network tab
- Trigger an event in your app, like a page view
- Look for a request to your worker subdomain (e.g.,
e.yourdomain.com) - Verify the response is
200 OK - Check the PostHog app to confirm events appear
If you see errors, check troubleshooting below.
Option 2: DNS and Page Rules
This method uses Cloudflare's DNS and Page Rules to route traffic without writing code. It requires a Cloudflare Enterprise plan.
- 1
Create a DNS CNAME record
Open your Cloudflare dashboard and follow Cloudflare's DNS records guide to create a CNAME record.
Configure the record:
- Name: Your subdomain, like
e - Target:
us-proxy-direct.i.posthog.comoreu-proxy-direct.i.posthog.comfor EU region - Proxy status: Proxied (configure by clicking the orange cloud icon)
The CNAME points your subdomain to PostHog's proxy endpoint. The orange cloud means Cloudflare will proxy the traffic instead of just doing DNS resolution.
Avoid obvious terms like
tracking,analytics,posthog, ortelemetryin your subdomain. Use something neutral likee,ph, oringestinstead. - Name: Your subdomain, like
- 2
Create a Page Rule to rewrite the Host header
In the Cloudflare dashboard, follow Cloudflare's Page Rules guide to create a new rule.
Configure the rule:
- URL pattern:
e.yourdomain.com/*(replace with your actual subdomain) - Setting: Host Header Override
- Value:
us-proxy-direct.i.posthog.comoreu-proxy-direct.i.posthog.com
The Host Header Override tells PostHog which domain the request is for. Without this, PostHog won't know how to route your request and you'll get 401 errors.
- URL pattern:
- 3
Update your PostHog SDK
In your application code, update your PostHog initialization to use your CNAME domain:
Replace
e.yourdomain.comwith your actual subdomain.The
ui_hostmust point to PostHog's actual domain so features like the toolbar link correctly. Verify your setup
CheckpointConfirm events are flowing through your DNS proxy:
- Open your browser's developer tools and go to the Network tab
- Trigger an event in your app, like a page view
- Look for a request to your subdomain (e.g.,
e.yourdomain.com) - Verify the response is
200 OK - Check the PostHog app to confirm events appear
If you see errors, check troubleshooting below.
Troubleshooting
Worker size limits exceeded
Cloudflare Workers have request and response size limits:
- Free plan: 10MB request, 50MB response
- Paid plans: Higher limits available
PostHog events can be up to 1MB and session recordings up to 64MB per message. On the free plan, very large recordings might hit the response limit.
If you see errors about size limits, upgrade to a paid Cloudflare plan or contact Cloudflare support about increasing limits.
User locations show as Cloudflare data center locations
If all your users appear to be in the same location, your Worker code is missing the IP forwarding logic.
The worker code in step 2 includes X-Forwarded-For header handling to preserve real user IPs. If you're using older worker code, update it to include this line in the forwardRequest function:
CORS errors in browser console
If you see No 'Access-Control-Allow-Origin' header errors:
For Workers: Add CORS headers to your worker code. Insert this before the export default line:
Then modify your handleRequest to use it:
For Page Rules: Add these settings to your Page Rule:
- Disable Security: On
- SSL: Full
- Disable Web Application Firewall: On
Warning: Be aware that disabling security features creates vulnerabilities. Only do this if CORS errors persist after trying other solutions.
301 redirects causing failures
If you see 301 Moved Permanently responses that cause CORS errors, your Cloudflare SSL mode is likely set to flexible.
PostHog requires HTTPS. When SSL is flexible, Cloudflare makes HTTP requests to the origin, which redirects to HTTPS. This redirect breaks CORS.
To fix this:
- In the Cloudflare dashboard, go to SSL/TLS
- Change the SSL mode to Full or Full (strict)
Unexpected token 'export' error in Worker
If your worker fails with Unexpected token 'export', you're using the wrong module format.
When creating a worker, Cloudflare offers two formats:
- Service Worker (older format)
- ES Modules (newer format, required for the code above)
The worker code in this guide uses ES Modules syntax. When creating your worker, make sure you select the ES Modules format, not Service Worker.
Page Rules not taking effect
If events aren't reaching PostHog with DNS and Page Rules:
- Verify your Page Rule URL pattern matches your subdomain exactly
- Check that the Host Header Override value matches PostHog's proxy domain,
us-proxy-direct.i.posthog.comoreu-proxy-direct.i.posthog.com - Confirm your DNS record's proxy status is set to proxied (orange cloud), not DNS only (gray cloud)
- Check that your CNAME target matches your PostHog region
Page Rules apply in order. If you have multiple rules, make sure the PostHog rule has priority.