Incomplete numbers are wrong numbers

Last updated October 27, 2026

Some of the people who visit your website never show up in your analytics.

That isn't a bug in any one tool. Many ad blockers stop analytics scripts along with the ads, including the privacy-friendly ones that set no cookies and sell nothing. The lists they use block analytics as a category, whatever a particular tool does.

How many visitors that hides depends on who your visitors are, and that's the trouble. The missing people are the ones your analytics can't see, so you can't correct for them by adding a few percent in your head. Your numbers are short by an amount you don't know.

Our first answer was no

The standard fix is a proxy. Your own website serves the analytics script and passes its data on, so to the browser it all looks like part of your site. Other privacy-friendly tools offer one. When we planned ours, we left it out, for three reasons that still seem fair to us.

Every visitor would come from the same place. Nodium learns a visitor's country and city from where their request comes from. Through a proxy, every request comes from your server, so every visitor would seem to live wherever your server is.

The obvious fix can be forged. A proxy can pass the visitor's real address along in a header. But a header is just text, and anyone can write any address into it. If we believed it blindly, anyone could skew your numbers or slip past the limits that protect the service.

Looking places up ourselves means a location database, which was one more thing to run.

So we wrote the gap down instead: a visit the script never reached isn't counted. That was accurate. It was also the wrong call.

Why we changed our minds

Nodium has a rule that runs through everything it counts: when in doubt, count less. An unclear case rounds down. A number that can't be checked isn't shown. The rule exists so that nothing is ever inflated.

But rounding down a case you can't be sure of is different from leaving out people you know were there. A visitor whose blocker stopped our script isn't a doubtful case. They loaded your page. They read it. Maybe they filled in your contact form. Leaving them out doesn't make the number cautious. It makes it wrong, and always low.

Where we draw the line

A fair objection: someone who installs an ad blocker has said something. Isn't counting them anyway ignoring what they asked for?

A block list is a list of domains that somebody else maintains. It stops scripts by where they come from, not by what they do. So it stops a tool that follows people across the web and sells what it learns, and it also stops one that does neither, because the list matches on the name. The visitor didn't choose to block Nodium. They installed a list, and the list matched us.

What a blocker is there to stop is tracking: cookies that follow you from site to site, and profiles built for advertisers. Nodium doesn't do either, through a proxy or without one. It stores nothing on the visitor's device and never stores their network address. Its visitor key changes every day and differs on every site, so it can't follow anyone anywhere. A visit counted through your proxy is treated exactly like any other.

There is one request we always honour, because it comes from the person and not from a list. A browser can send a signal that means "don't track me": Do Not Track, or the newer Global Privacy Control. When it does, Nodium's script sends nothing at all, and that holds through a proxy too. The proxy changes which road the data takes. It doesn't change whether someone asked us not to send it.

You may draw that line elsewhere. If so, leave the proxy off: it's your choice for your site. Our WordPress plugin turns it on by default, with a box to untick. Everywhere else you set it up yourself.

How it works

The proxy lives on your own domain, under a short path made from your site key: six letters and digits, chosen to avoid the words block lists look for. Your server fetches Nodium's script from us and hands it to the browser as if it were your own file. The script reports to that same path, and your server passes each report on to us.

That brings back the problem that stopped us the first time: every request now comes from your server.

Your server signs the visitor's address. Each site gets a secret key. When your server passes an event on, it attaches the visitor's address and the time, signed with your key. We check the signature. If it doesn't match, or the time is more than five minutes out, we throw the whole event away. It isn't counted at all, rather than counted under the wrong address. Without your key, nobody can forge an address that passes.

We use the address, then forget it. The address does what it always did. It goes into the day's visitor key, it lets you exclude your own visits, it keeps the rate limits honest, and it tells us roughly where the visitor is. Then it's gone. We never store it, through a proxy or without one.

We look places up ourselves. For a proxied visit, the country, region and city come from DB-IP's location data, which we hold, so the address is never sent to anyone else to look up. It's a different source from the one we use for direct visits, so now and then the same city is named slightly differently. Our How we count page says so, and credits DB-IP.

Which servers can sign

The signature needs a little code running on your server.

  • WordPress: the Nodium plugin sets up the proxy for you. It also skips your own visits while you're signed in as an admin.
  • Cloudflare: a short Worker, which we give you to paste in.

Some hosts can forward requests but can't run code. That covers Vercel and Netlify rewrites and a plain Nginx config. They can pass the visitor's address in the standard X-Forwarded-For header, but they can't sign it. For those, there's a setting called "Accept addresses forwarded by my server", and it states its cost: anyone who knows your site key could then send visits that look like they came from other addresses. Your site key is in your pages' code, so it isn't a secret. That's the trust any unsigned proxy asks for.

We also limit how much any one proxy address can send, then how much it can send for one site, then how much the visitor behind it can send. That last limit matters. Before release, our own review found that a forged forwarding header could let a single browser send fifty times more events than it should. We fixed it, and a test keeps it fixed.

What the number means now

Settings shows when a proxied visit last arrived, so you can see the proxy working. The rule on How we count reads, in full:

A visit the script never reached isn't counted: some ad blockers stop it, and it sends nothing under Do Not Track or Global Privacy Control. A proxy on your own domain lets it reach the visitors whose blockers stopped it; the browser's own request not to be tracked is always kept.

Your numbers still aren't perfect. A visitor with JavaScript off is invisible, and so is anyone who asked not to be counted, as they should be. A blocker set to stop your own domain's paths would stop the proxy too, and some WordPress security plugins block the routes the plugin uses. But on a site with the proxy on, an ad blocker's list no longer hides a visitor just because it knows our name.

The proxy steps for each platform are in our docs.