Module 4, Lesson 6: Bypass Ad Blockers with PostHog's Reverse Proxy

Module 4, Lesson 6: Bypass Ad Blockers with PostHog's Reverse Proxy

12 min read min

In this lesson, I’m picking up directly from where we left PostHog. Analytics is installed and working — but right now it only captures visitors who don’t have an ad blocker. This lesson fixes that.


Before We Start

Here’s what I’d expect you to have in place before this lesson:

From previous lessons:

  • PostHog is installed and capturing events on your live landing page — you did this in Module 4, Lesson 5
  • Your domain is on Cloudflare (or you can set it up there) — this is what enables the automatic proxy configuration
  • The GitHub → Vercel deployment pipeline is working — push to GitHub, Vercel deploys (Module 4, Lesson 3)
  • Claude Code is installed and running in your WebStorm terminal (Module 3, Lesson 3)

Tools / setup you’ll need:

  • WebStorm (or VS Code) with Claude Code running in the terminal
  • A PostHog account — you set this up in Module 4, Lesson 5
  • Your domain’s DNS managed by Cloudflare — this is what makes the automatic proxy setup work

By the end of this lesson, you’ll:

  • Understand why ad blockers silently break your analytics
  • Have PostHog’s reverse proxy set up through your own domain
  • Know how to update the PostHog API host in your codebase and deploy the change
  • Be able to verify the proxy is actually working — with an ad blocker enabled

About This Lesson

Duration: ~7 minutes video + ~15 minutes practice Skill Level: Intermediate What You’ll Build: A reverse proxy configuration that routes PostHog tracking data through your own domain, making it invisible to ad blockers.

This is a short lesson but an important one. Most people skip it and then wonder why their analytics don’t add up. I want you to have this fixed before you start driving traffic — because once visitors start coming, you want to know you’re capturing all of them, not just the ones without ad blockers installed.


Watch the Lesson

<iframe src=“https://iframe.mediadelivery.net/embed/605189/c38df7c9–7267–4bed-a22a-f1301d70551f” allow=“autoplay; fullscreen; picture-in-picture” allowfullscreen loading=“lazy” style=“width:100%; aspect-ratio:16/9; border:none; border-radius:8px;”>


ℹ️
How I’d suggest going through this: Watch the video first without stopping — just get the big picture. Then come back here and work through the guide section by section, pausing the video at the timestamps I’ve noted. After that, this guide is your reference — you shouldn’t need to rewatch to look something up.

What We’re Covering

Here’s what I’m walking you through in this lesson and why it matters:

  • The ad blocker problem — why a significant chunk of your visitors are invisible to PostHog right now
  • How a reverse proxy fixes it — routing analytics traffic through your own domain so it doesn’t look like PostHog
  • Setting up the proxy in PostHog — using PostHog’s managed proxy feature with Cloudflare
  • Updating your codebase — one config change to tell PostHog to use your proxy endpoint
  • Verifying it works — testing with an ad blocker actually enabled

1. The Problem: Ad Blockers Kill Your Analytics (~0:00)

At this point your PostHog setup is live. If you go into your project and hit Activities, then refresh your landing page, you should see events coming in — pageviews, clicks, whatever you’ve got set up.

But here’s the thing: that only works if the person visiting your page doesn’t have an ad blocker running. And a lot of people do. In tech audiences especially, it’s not unusual for 30–40% of visitors to be blocking trackers. On mobile — particularly on iPhone, where content blocking is on by default in many configurations — it’s even higher.

Those visitors don’t show up in your analytics at all. Not as bounces, not as low-engagement sessions — they’re just not there. Which means you’re making decisions based on a partial picture.

PostHog has a built-in solution for this: a reverse proxy. Instead of your page sending tracking data directly to app.posthog.com — which ad blockers recognise and block — you route it through a subdomain on your own domain. From the browser’s perspective, it looks like data going to your own site. Ad blockers can’t distinguish that from regular network traffic.


2. How the Reverse Proxy Works

2.1 The Core Idea

Right now, your PostHog script sends events to PostHog’s servers. The URL it hits — app.posthog.com — is on every ad blocker’s list. So the blocker intercepts it and drops the request.

With the reverse proxy, you set up a subdomain on your domain — something like g.yourdomain.com — that silently forwards all that traffic to PostHog on the backend. Your visitor’s browser sends data to g.yourdomain.com. Your server (Cloudflare, in this case) receives it and passes it along to PostHog. The visitor’s ad blocker sees a request to your own domain and lets it through.

The key thing PostHog tells you when setting this up: don’t use anything that makes it obvious it’s an analytics proxy. Don’t call the subdomain analytics.yourdomain.com or tracking.yourdomain.com. Use something generic — g, t, a, whatever. I used g.noturo.app in the lesson. It looks like any other request to my domain.

2.2 Why Cloudflare Makes This Easy

PostHog’s managed proxy feature integrates directly with Cloudflare. Instead of manually configuring DNS records and worker scripts yourself, PostHog can connect to your Cloudflare account and handle the configuration automatically. That’s what we’re using here.

If your domain isn’t on Cloudflare, you can still set up a reverse proxy — PostHog has documentation on doing it manually — but the automatic setup I’m showing only works when Cloudflare manages your DNS.


3. Set Up the Reverse Proxy in PostHog (~0:50)

3.1 Find the Proxy Settings

In your PostHog dashboard, look for the reverse proxy option. It’s typically under your project settings. You’re looking for something called Managed Proxy or Reverse Proxy — it may be labelled as beta, which is fine.

Click Add or Set Up Reverse Proxy.

3.2 Choose Your Subdomain

PostHog will ask you for a domain. This is the subdomain you want to use as your proxy endpoint.

Enter something generic. I used g.nurturo.app. The format is:

[something-generic].[your-domain].[tld]

Don’t use posthog, analytics, tracking, or anything that signals what it’s doing. Keep it short and ambiguous.

⚠️
PostHog explicitly warns you about this: use a generic subdomain name. Ad blockers maintain lists of known analytics proxies, so an obvious name defeats the purpose. Something like g, t, or a as the subdomain prefix is fine.

3.3 Configure via Cloudflare

Once you’ve entered your subdomain, PostHog will offer to configure it automatically. Click Configure Automatically.

It’ll ask you to authorise your Cloudflare account. Go through the authorisation flow — log into Cloudflare if prompted, approve the connection, and let PostHog do its thing.

This sets up the necessary DNS records and Cloudflare Worker configuration automatically. You don’t need to touch Cloudflare yourself.

ℹ️
The managed proxy feature was in beta when I recorded this. By the time you’re watching, it may be fully released — which might mean a slightly different UI. The concept is the same regardless: enter your subdomain, connect Cloudflare, let it configure itself.

Once it completes, your proxy endpoint is live. But we’re not done — your app is still pointing at PostHog’s servers directly. We need to update that.


4. Update the PostHog API Host in Your Code (~2:55)

4.1 What Needs to Change

When your landing page initialises PostHog, it sends data to an API host. Right now that’s pointing to PostHog’s servers. We need to change it to point to your new proxy subdomain.

The config change is small — it’s a single URL — but it has to be in your code and deployed before the proxy does anything useful.

4.2 The Prompt

Open Claude Code in your WebStorm terminal. Use this prompt:

Update my PostHog API host to https://g.yourdomain.app

Replace g.yourdomain.app with whatever subdomain you set up in step 3.2. Claude will find the PostHog initialisation in your code and update the API host value.

If you’re comfortable finding it yourself, it’s usually in your main JS/TS file — look for the PostHog init() call. The option you’re changing is api_host. But letting Claude handle it is faster and you’re less likely to miss a second reference somewhere.

4.3 Commit and Deploy

Once Claude has made the change, tell it:

commit and push this

This commits the change and pushes to GitHub. Vercel picks it up automatically and deploys. Go to GitHub and look for the green checkmark next to the latest commit — that tells you the Vercel deployment succeeded.


5. Verify It’s Actually Working (~3:50)

This is the part I think is important to actually do — not just assume it’s working. Here’s how I tested it in the lesson.

5.1 Confirm Tracking Works Without an Ad Blocker First

Go to your PostHog dashboard and open the Live view under Activities. Then open your landing page in an incognito window and refresh a few times.

You should see events appearing in the Live view — something like “1 recently online” or “1 recently active recording.” That confirms PostHog is tracking without an ad blocker in play.

Close the incognito window.

5.2 Enable an Ad Blocker

Now enable an ad blocker. I use AdGuard — but whatever you have works. The point is to simulate what a real visitor with a blocker would look like.

With the blocker on, go back to PostHog Live view, then open your landing page and refresh.

Without the proxy, you’d see nothing. PostHog would show no activity from you. That’s the problem we just fixed.

5.3 Test With the Proxy Live

Make sure your Vercel deployment is complete (green checkmark on GitHub). Then repeat the test — ad blocker still on, refresh your landing page, watch the PostHog Live view.

You should now see events coming through, even with the blocker active. That’s the proxy working.

If you’re seeing events in PostHog Live view with an ad blocker enabled — the proxy is working. You’re now capturing a much more complete picture of who’s visiting your page.

6. Try It Yourself

Exercise 1: Set Up the Proxy End-to-End

What to do: Follow sections 3, 4, and 5 above. Set up the managed proxy in PostHog, update the API host in your code via Claude Code, deploy, and verify with an ad blocker enabled.

A nudge if you’re stuck: The most common issue is the Cloudflare authorisation step. Make sure you’re logged into the right Cloudflare account — the one that manages the domain you’re using for your landing page. If PostHog can’t find your domain, check which account your DNS is under.

How you’ll know it’s working: PostHog Live view shows activity from your browser even when an ad blocker is enabled.

Exercise 2: Check Your Network Requests

What to do: With your landing page open in Chrome, open DevTools (F12 / Cmd+Option+I), go to the Network tab, and filter by your proxy subdomain (e.g. g.noturo.app). Refresh the page.

What you’re looking for: Requests going to your subdomain — not app.posthog.com. If you see them, the proxy redirect is happening client-side too.

What this is practising: Understanding what the proxy actually does at the network level. Useful for debugging if something stops working later.


7. You Should Be Able to Do This Now

Here’s what you can put together with what we just covered:

  • Set up PostHog’s managed reverse proxy for any project using Cloudflare DNS
  • Update a PostHog API host configuration using Claude Code
  • Verify analytics integrity using PostHog’s Live view — with and without an ad blocker

Check Yourself

  • I’ve set up the PostHog managed proxy with a generic subdomain name
  • Cloudflare authorisation went through and the proxy configured automatically
  • I’ve updated the api_host in my PostHog init to point to my proxy subdomain
  • I’ve committed and pushed the change — Vercel shows a green checkmark
  • I’ve tested with an ad blocker enabled and can see events in PostHog Live view
If those boxes are ticked, your analytics are solid. You’re capturing as complete a picture as you can get. Next up, we push traffic to this page.

If Something’s Not Working

⚠️
Cloudflare authorisation fails or PostHog can’t find your domain
What’s happening: PostHog is connecting to Cloudflare but can’t find the domain you entered. This usually means the domain is under a different Cloudflare account, or DNS isn’t managed by Cloudflare at all.
How to fix it: Log into Cloudflare and confirm your domain is listed there under the account you authorised. If your domain is at a different registrar with its own DNS (not using Cloudflare nameservers), you’ll need to either move DNS to Cloudflare or follow PostHog’s manual proxy setup docs.
⚠️
Events still not showing with ad blocker on after deploying
What’s happening: Either the deployment hasn’t completed yet, or the api_host in your code still points to PostHog’s servers.
How to fix it: First, check GitHub — confirm the green checkmark is there. Then check your PostHog initialisation in your codebase and make sure api_host is set to your proxy subdomain (e.g. https://g.yourdomain.app). If it still says https://app.posthog.com, the Claude Code update didn’t take or wasn’t committed.
⚠️
Proxy subdomain is getting blocked
What’s happening: You may have used a name that’s on a common blocklist — like analytics, tracking, or posthog as the subdomain prefix.
How to fix it: Go back into PostHog’s proxy settings and change the subdomain to something completely generic — g, t, x. Then update api_host in your code to match and redeploy.

The Short Version

Here’s what I want you to walk away with:

  • Ad blockers silently drop PostHog requests — without a proxy, you’re missing a significant slice of your traffic
  • The fix is a reverse proxy — route analytics traffic through your own domain so it looks like a request to your own site
  • PostHog’s managed proxy makes it easy — connect Cloudflare, pick a generic subdomain, done
  • One code change to wire it up — update api_host in your PostHog init, commit, push, deploy
  • Always verify with a real ad blocker — don’t assume it’s working; test it

Quick Reference

The Proxy Subdomain Format

g.[yourdomain].[tld]

Generic. Nothing that signals analytics or tracking. Short prefix is fine.

The Code Change

In your PostHog init() call, update api_host:

posthog.init('phc_YOUR_KEY', {
  api_host: 'https://g.yourdomain.app',
  // ... other options
})

The Claude Code Prompt

Update my PostHog API host to https://g.yourdomain.app

Then:

commit and push this

How to Test

1. Open PostHog → Activities → Live view
2. Open your landing page in incognito → confirm events appear (no blocker)
3. Enable your ad blocker
4. Refresh your landing page
5. Check PostHog Live — you should still see events

Resources

Tools Used

  • PostHog — product analytics, installed in Module 4, Lesson 5
  • Cloudflare — DNS and proxy infrastructure
  • Claude Code — AI coding assistant, running in the WebStorm terminal
  • Vercel — deployment pipeline, connected since Module 4, Lesson 3

Questions I Get Asked

Q: Do I really need to do this? How many people actually use ad blockers?

More than you’d think — and it skews toward the exact demographic you’re probably targeting. Tech-savvy users, developers, and privacy-conscious people are much more likely to run ad blockers than the general public. If your product is aimed at that audience (and many of ours are), you could easily be missing 30–50% of visitors in your analytics without this fix.

Q: My domain isn’t on Cloudflare — can I still do this?

Yes, but you’ll need to do it manually. PostHog has documentation for setting up a reverse proxy with other providers like AWS CloudFront or NGINX. The automatic setup I showed only works with Cloudflare. If you want to keep things simple, the easiest path is to move your domain’s DNS to Cloudflare nameservers — it’s free and doesn’t change where your domain is registered, just where DNS is managed.

Q: What if the proxy subdomain itself gets added to a blocklist eventually?

It’s possible, but unlikely if you use a generic name. Ad blocker lists tend to target known tracking domains — not arbitrary subdomains on random people’s domains. And if it does happen, changing the subdomain and redeploying takes about 10 minutes.

Q: Does this have any performance impact on my landing page?

Negligible. The reverse proxy adds one hop in the network path for analytics requests, but those happen asynchronously in the background. They don’t block the page load or affect what your visitor sees.

Q: How do I know I’m ready for the next lesson?

Analytics works with ad blockers on. That’s the bar. When you can see events in PostHog Live view with AdGuard or uBlock or whatever you’re using enabled — you’re done here.


💬 Stuck? Come Talk to Us

💡
If something’s not clicking or you want to share what you’ve built — come find us in Discord.
The Product Path community → https://discord.gg/RFXRf9yg
Drop your question in the right channel. The community’s active and I check in there too.

Glossary

Reverse proxy: A server (or in this case, a Cloudflare Worker) that sits between your visitor’s browser and a third-party service. The browser sends requests to your domain; the proxy forwards them on to the real destination. From the browser’s perspective, it’s just talking to your site. (introduced in Module 4, Lesson 6)

Managed proxy (PostHog): PostHog’s built-in feature for automatically setting up a reverse proxy via Cloudflare. PostHog connects to your Cloudflare account and configures the DNS records and Worker routing on your behalf. (introduced in Module 4, Lesson 6)

Ad blocker: A browser extension or OS-level tool that blocks network requests to known tracking and advertising domains. Common examples include uBlock Origin, AdGuard, and Brave’s built-in shield. Blocks PostHog by default unless a reverse proxy is in place. (introduced in Module 4, Lesson 6)

API host (PostHog): The URL PostHog’s JavaScript SDK sends event data to. Defaults to https://app.posthog.com. Changed in this lesson to your proxy subdomain so requests are routed through your own domain instead. (first introduced in Module 4, Lesson 5 — Setup PostHog)

Cloudflare Worker: A serverless function that runs at Cloudflare’s edge — used here to receive analytics requests from your subdomain and forward them to PostHog. Set up automatically by PostHog’s managed proxy feature. (introduced in Module 4, Lesson 6)

Discuss this lesson in the community