Skip to content
← All articles

Analytics & Tracking

Ghost Traffic in GA4: Sessions From Visitors Who Were Never There

Your measurement ID is public, so anything that can read it can send events that look like real sessions. How to confirm it with the hostname dimension, and how to get a clean number in BigQuery and Looker Studio.

September 23, 2026 · 10 min read

A solid line of real sessions below a dashed, inflated line, the gap between them shaded

Your sessions are up forty percent and nobody on the team did anything to earn it. Engagement rate has collapsed. Half the new traffic is from a country you do not sell into, every one of those sessions lasted zero seconds, and the landing page they all supposedly hit does not exist on your site.

That is not a growth spurt. That is ghost traffic, and the reason it is worth your attention is not that it looks untidy. It is that every number downstream of it is now wrong: conversion rate, channel performance, the report you send the board, and the numbers your bidding algorithms are learning from.

Here is what is actually happening, how to confirm it in your own property, and how to get to a clean number in BigQuery and Looker Studio.

What is ghost traffic in Google Analytics?

Two different things get called bot traffic, and they need different fixes.

Crawlers and automated browsers genuinely request your pages. Scrapers, uptime monitors, headless browsers running someone's test suite, AI crawlers collecting training data. They hit your server, they sometimes execute your JavaScript, and when they do they get counted like anyone else.

Ghost traffic never touches your site at all. Your measurement ID sits in the page source of every page you serve, in plain text, because it has to. Anything that can read that ID can send events that arrive in your property looking like they came from a real browser. No request to your server, no entry in your access logs, no way for your site to refuse. The hits simply appear.

That second category is the one that produces the strange signatures: sessions on pages you have never published, referrals from domains selling something, traffic from cities you have no presence in. Nobody went anywhere. Somebody sent Google a packet with your ID on it.

Most of it is not aimed at you personally. It is spray, sent at every measurement ID somebody scraped that week, usually to get a domain name in front of whoever opens the referral report.

Why does GA4's bot filtering not catch it?

GA4 does filter bots automatically, using the IAB known bots and spiders list. It runs on every property, it cannot be switched off, and it is genuinely useful.

The operative word is known. That list identifies well behaved automated traffic that declares itself in the user agent: Googlebot, Bingbot, the big commercial crawlers. It works because those bots are honest about being bots.

Ghost traffic is not on the list and never will be, because there is no crawler to identify. It is a forged hit claiming to be Chrome on a Windows laptop in a city with a plausible name. Nothing in the payload says bot, so nothing filters it.

This is why people check the bot filtering box, see it is already on, and conclude the problem must be real traffic. It is not. The filter is doing its job on a category this traffic does not belong to.

How do I spot ghost traffic in GA4?

Hostname is the single most useful dimension, and most people never look at it.

Every hit reports the hostname it claims to have occurred on. Real traffic reports your domain. Ghost traffic reports whatever the sender put in the field: a blank, a nonsense string, somebody else's domain, or occasionally nothing at all. It is the closest thing to a smoking gun that GA4 gives you.

To check it, build a free form exploration with Hostname as the row dimension and Sessions as the metric, over the last few months. You should see your domain, possibly a staging subdomain, and nothing else. Anything else in that list is traffic you did not serve.

Four other signals worth checking, each of which is weak alone and damning together:

  • Zero engagement time across a whole segment. Real people vary. A source where every single session recorded no engaged time is not a source of people.
  • Landing pages that do not exist. Add Landing page to the same exploration. Paths you have never published cannot have been visited.
  • A geography with no commercial logic and no corresponding ad spend. By itself this means nothing. Combined with zero engagement it means a great deal.
  • A step change rather than a ramp. Real growth has a shape. Ghost traffic starts on a Tuesday and stops three weeks later just as abruptly.

Check Realtime while a spike is in progress if you catch one live. Sessions appearing with no corresponding entries in your server access logs settle the question immediately, because traffic that never reached your server did not come from a browser.

How do I stop it at the source?

Partly you cannot, and it is worth being honest about that before you spend a week trying.

You cannot stop somebody sending forged hits to Google with a public identifier. The identifier has to be public for measurement to work at all. There is no setting that closes that door.

What you can do at the property level:

  • Internal traffic filters remove your own team by IP. Worth doing, but it is a different problem.
  • Developer traffic filters remove debug mode sessions. Also worth doing, also a different problem.
  • A fresh property with a new measurement ID genuinely helps in one specific case: when a single spammer has locked onto your current ID and will not stop. You lose your history in that property, which is usually too high a price, and you are back in the same position when the new ID gets scraped.

Moving collection server-side gives you more control over what gets accepted, since you own the endpoint and can reject what does not look right. It is a real improvement and it is a project, not an afternoon.

For almost everyone the practical answer is different. Stop trying to keep bad rows out of GA4, and stop reporting from a layer that contains them.

How do I filter ghost traffic in BigQuery?

This is where the problem becomes tractable, because in BigQuery you are looking at rows and you can decide which ones count.

Turn on the GA4 BigQuery export if it is not already on. It is in Admin, under Product links, and it costs nothing to enable. From that point every event lands as a row you can query, including the ones you would rather not have.

The hostname lives inside the event parameters rather than at the top level, so pull it out of page_location:

SELECT
  NET.HOST(
    (SELECT value.string_value FROM UNNEST(event_params)
     WHERE key = 'page_location')
  ) AS hostname,
  COUNT(*) AS events
FROM `your_project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260601' AND '20260923'
GROUP BY hostname
ORDER BY events DESC

Run that first and read the output before you filter anything. You are looking for the list of hostnames your property believes it is collecting for. Your domain will be at the top. What follows it is the thing you are removing.

Then wrap the exclusion in a view, rather than writing the condition into every query you ever run again:

CREATE OR REPLACE VIEW `your_project.analytics_clean.events` AS
SELECT *
FROM `your_project.analytics_123456789.events_*`
WHERE NET.HOST(
  (SELECT value.string_value FROM UNNEST(event_params)
   WHERE key = 'page_location')
) IN ('www.yourdomain.org', 'yourdomain.org')

A view costs nothing to store and is always current. If your queries get slow or expensive, swap it for a scheduled query writing a partitioned table, but start with the view.

Two things to get right. Allow rather than deny: list the hostnames you accept instead of the ones you reject, because next month's spam will use a hostname you have not seen and an allow list already excludes it. And include every hostname you genuinely serve, your www and bare domain, any subdomain, any separate donation or checkout domain, or you will quietly delete real traffic and create a worse problem than the one you started with.

If a chunk of junk shares your hostname, which happens when somebody has copied your page source onto their own site, add a second condition on engagement. Sessions with no engaged time and a single event are a reasonable next cut, but look at what it removes before you trust it.

How do I keep it out of Looker Studio?

Whichever source you use, set the filter once, where every chart inherits it. A filter added to individual charts will be correct on the charts you remembered and wrong on the one somebody adds next quarter.

If you are reporting from the BigQuery view, you are done. Point the data source at analytics_clean.events and every chart in every report built on it is clean by construction. This is the reason to do the work in BigQuery rather than in the dashboard.

If you are reporting from the GA4 connector and BigQuery is not an option yet, add a filter at the data source level on Hostname, set to include your domains. It is not as robust, because it only cleans what that connector serves and each new data source needs the same filter applied again, but it is much better than nothing and it takes five minutes.

Then label it. A page footer saying which hostnames the report includes, and from what date, saves the conversation where somebody compares the dashboard to the GA4 interface, finds different numbers, and concludes the dashboard is broken. It is not broken. It is the one that is right, and the note is what lets the next person know that.

What about the history that is already polluted?

Your GA4 reports will not get better. Processed data is not editable, and filters apply from the moment you switch them on, never backwards. Whatever is in the interface for last quarter is what it will always say.

BigQuery is different, and this is the strongest argument for the export. The raw rows are all there, including everything collected before you understood the problem. The moment your view exists it applies to the full history, so you can restate the last year cleanly and compare like with like.

If the export was not on during the polluted period, that history cannot be recovered. Turn it on today, so that the next time a question like this comes up you have rows to go back to rather than a report you cannot amend.

One last thing worth saying out loud to whoever reads your numbers: when you clean this up, traffic will fall. Sometimes considerably. That drop is not a loss, it is the correction, and it is much easier to explain in advance than to defend a month later.

FAQ

What is ghost traffic in Google Analytics?

Ghost traffic is hits sent directly to Google Analytics using your measurement ID, without anyone ever visiting your site. Your measurement ID is public in your page source, so anything that can read it can send events that look like real sessions. Nothing reaches your server, so your access logs show no trace of the visits that appear in your reports.

Does GA4 automatically filter bot traffic?

GA4 filters known bots and spiders from the IAB list automatically, on every property, and you cannot switch it off. It only catches automated traffic that identifies itself as automated. Ghost traffic is a forged hit claiming to be an ordinary browser, so there is nothing in it for the filter to recognise.

How do I check for ghost traffic in GA4?

Build a free form exploration with Hostname as the dimension and Sessions as the metric. Every hit reports the hostname it claims to come from, and real traffic reports your domain. Anything else in that list is traffic your site never served. Confirm it with landing pages that do not exist on your site and segments where engagement time is zero across every session.

Can I remove bot traffic from GA4 historical data?

No. GA4 data filters apply only from the time you activate them, and processed reports cannot be edited afterwards. The BigQuery export is the way around it, because the raw event rows are all retained and a filtered view applies to the entire history at once, letting you restate past periods on the same basis as current ones.

How do I filter bot traffic in Looker Studio?

Report from a filtered BigQuery view rather than from the GA4 connector, so the exclusion lives in the data rather than in the dashboard. If you must use the connector, add a hostname filter at the data source level so every chart inherits it, and document which hostnames and which date range the report covers.

Want help with Google Analytics Audit?

A line-by-line review of your Google Analytics 4 property, events, and conversions, so you know which numbers you can scale on.

More reading

Advanced Tracking

September 24, 2026 · 10 min read

Shopify's Meta CAPI Is Not Server-Side Tracking

Shopify really does send Conversions API events, and that still is not a server-side tagging layer. What the native channel cannot do, what owning the layer changes, and how to implement server-side GTM with Stape without double counting every order.

Read article
Advanced Tracking

September 23, 2026 · 9 min read

Basic or Advanced Consent Mode: What Each One Costs You

Both are Consent Mode, and they behave completely differently before a visitor chooses. What advanced mode really sends, what conversion modelling needs in return, and how to tell which one you are actually running.

Read article