What a reverse proxy is actually doing here
Without a proxy, the browser sends capture requests straight to PostHog's own domain (Cloud or self-hosted equivalent). Every popular ad blocker and privacy browser ships a filter list that recognizes analytics-vendor domains and drops the request client-side - before it ever leaves the page. A reverse proxy rewrites that request so it appears to come from a path on your domain (for example yourapp.com/ingest), which most blocklists do not target because they cannot distinguish it from any other first-party API call.
This is purely a network-routing change. It does not alter what PostHog captures, how events are processed, or your event schema - it changes which domain the browser talks to.
When you actually need this
- Measurable capture loss. If PostHog's own comparison of ingested events versus server-side or backend-confirmed events shows a gap, ad-blocker loss is the first thing to rule out - this requires a real before/after measurement on your traffic, not an assumed percentage; published ad-blocker adoption rates vary by audience and should not be treated as your number.
- Safari and ITP-affected traffic. Safari's Intelligent Tracking Prevention treats some third-party analytics cookies more aggressively than first-party ones regardless of ad blockers.
- Corporate or education network filtering. Some managed networks block known analytics domains at the DNS or proxy layer, independent of the browser.
Implementation done properly
Pick the path that matches how your app is already deployed - do not add a new piece of infrastructure just to run a proxy if your framework or edge platform already has a documented pattern for it.
| Deployment shape | Proxy approach | What to verify before shipping |
|---|---|---|
| PostHog Cloud + Vercel/Cloudflare/Netlify | Managed reverse proxy through the platform's own edge config | Confirm the platform's managed-proxy feature is still current - this is a vendor feature that changes; check current docs for exact setup steps and plan availability |
| Next.js | Route handler or middleware rewrite to the ingestion path | Middleware must not run on every route if it adds latency to unrelated requests |
| Nuxt / SvelteKit | Server route or hook rewriting the capture endpoint | Confirm the rewrite preserves query params PostHog needs (project key, compression flag) |
| Self-managed edge (nginx, Caddy, Cloudflare, Kubernetes ingress) | A dedicated location/route block proxying to PostHog's ingestion host | TLS termination and header forwarding (especially client IP) configured correctly |
| PostHog self-hosted | Running PostHog itself behind your own reverse proxy for the app UI, separate from ingestion proxying | Distinguish "proxying ingestion for ad-blocker evasion" from "proxying the self-hosted app for TLS/access" - different problems, same word |
Version and platform specifics move: PostHog has shipped and later deprecated specific managed-proxy integrations before. Verify the exact setup steps against current PostHog docs rather than a screenshot from an older guide - this page describes the routing concept and the decision framework, not a copy-pasteable config.
How to verify it worked
- Open browser devtools network tab on a page with a common ad blocker enabled and confirm the capture request now shows your domain, not PostHog's.
- Confirm the request still returns a success status and the event shows up in PostHog's live events view within the normal ingestion delay.
- Re-run whatever gap measurement flagged the problem originally (backend event count vs. PostHog-captured count) and confirm the delta actually closed - a proxy that is misconfigured can silently fail closed, which looks identical to "no problem" until you check the count.
- Check for CORS or mixed-content errors in the console - a proxy that changes domains can break assumptions elsewhere in the page if it is not scoped narrowly to the ingestion path.
Proxy work often surfaces alongside a self-hosted deployment decision, since both involve owning more of the network path than PostHog Cloud's default.