Contents11
- What GA4 showed
- The screen size gave it away
- Cloudflare’s free plan without ASN
- The beacon’s Chrome versions against real readers
- Which posts the bot was reading
- What I changed, and what I left
- FAQ
- Why does GA4 show so many direct visits from Singapore?
- Doesn’t GA4 exclude bots automatically?
- Can I see the ASN of a visitor on Cloudflare’s free plan?
- Should I block Singapore at the WAF?
I was about to add affiliate links to my old gadget reviews. GA4 had them as my most-viewed pages, a mini tripod review on top with a mouse review and a Walkman review close behind, and if anything on this site was going to earn from Amazon, it looked like those.
Then I sorted GA4 by country. Singapore was first by a wide margin, with 210 sessions in 28 days. Across those 210 sessions GA4 counted one engaged session and zero seconds of engagement.
Cloudflare’s free plan does not tell you which network a request came from, so I could not look up the ASN and see a data center. This post is how I settled it anyway, using this site’s own data, and what the page ranking looked like once the bot was out.
What GA4 showed
GA4 recorded the Singapore traffic as ordinary visits, and the pattern did not look like people. Last 28 days, Singapore only:
| Metric | Singapore |
|---|---|
| Sessions | 210 |
| Engaged sessions | 1 |
| Total engagement time | 0 s |
| Channel | Direct, all of it |
| Pages per session | 1 |
Every session was a new user who opened one page and left. That shape, session_start ≈ first_visit ≈ page_view, is the one I had already learned to distrust when my AI-crawler numbers turned out to be scanners. Readers in Japan, by contrast, scroll and stay.
Low engagement alone proves nothing, though. Plenty of people open a page and leave. I wanted something a person would not produce.
The screen size gave it away
The strongest single signal was in GA4 itself: 201 of the 210 Singapore sessions reported a screen resolution of 1280x1200.
That is an odd display. It is nearly square, matches no common laptop or phone, and real visitors spread across dozens of resolutions. When almost every session from one country reports the same unusual value, you are looking at a configuration rather than an audience.
| Dimension (Singapore, 28 days) | Value | Sessions |
|---|---|---|
| Screen resolution | 1280x1200 | 201 of 210 |
| Browser / OS | Chrome / Windows | 201 of 210 |
| City | Singapore | 210 of 210 |
| Landing page | 2021–22 review and how-to posts | almost all |
Filtering the whole property on 1280x1200 back to May puts the first session on 2026-06-18. It ramped to 17 a day within four days and has run at 5 to 12 a day since, 556 sessions from Singapore with that screen so far. A scattering of sessions with the same resolution came from about twenty other countries, one or two each. Those could be anything, and I left them alone.
Cloudflare’s free plan without ASN
The usual next step is to look up the network. If the requests come from a cloud provider rather than a consumer ISP, you are done. On a free zone that is not available. Asking the GraphQL Analytics API for clientAsn returns:
zone '...' does not have access to the field 'clientasndescription'
What the free plan does give you is the user agent, path, status and hour of each request group. And if Cloudflare Web Analytics is on, there is one path that only a JavaScript-executing client hits: the beacon at /cdn-cgi/rum. Grouping that path by user agent shows which clients actually ran the page, which are the same ones that fire a GA4 tag.
QUERY = """
query($zone: String!, $since: Time!, $until: Time!, $country: String!) {
viewer { zones(filter: { zoneTag: $zone }) {
httpRequestsAdaptiveGroups(
limit: 5000,
filter: {
datetime_geq: $since, datetime_lt: $until,
clientCountryName: $country,
clientRequestPath: "/cdn-cgi/rum"
}
) { count dimensions { userAgent datetimeHour } }
} }
}
"""
The free plan caps each query at one day, so I ran it for seven one-day windows and summed. Then I ran the same query for Japan as a control group.
The beacon’s Chrome versions against real readers
The control group is what settled it. Seven days of beacons at /cdn-cgi/rum:
| Singapore | Japan (control) | |
|---|---|---|
| Beacons | 147 | — |
| OS | Windows, all 147 | Windows |
| Chrome versions | 103 to 133, more than a dozen different majors | 153 and 154 |
| Hours (UTC) with a beacon | 23 of 24 | a few, clustered |
Chrome updates itself, and my readers in Japan are on 153 and 154, the current releases as of October 2026. One browser stuck on 103 would be unusual. A stream of visitors from one city spread evenly across 103, 105, 106, 107, 109, 110, 111, 112, 116, 117, 131 and 133 is a tool picking a user agent from a list.
The hours point the same way. Singapore’s beacons arrive in 23 of 24 UTC hours with no night-time dip, and a city does not read like that.
The rest of Singapore’s 845 requests in those seven days fit too. 277 came from Linux x86_64 Chrome 131, a common headless default. 154 were Huawei’s PetalBot. 273 were 403s from my path rule on things like /admin/.env and /phpinfo.php, sent under the same rotated Windows Chrome strings.
None of this directly proves a data center, and I would rather say so than overclaim. But screen size, browser version, hour of day and engagement all point the same way, and no single human explanation covers all four.
Which posts the bot was reading
The bot did not spread evenly over the site. It went almost entirely to old review and buying-guide posts. Its pageviews by page over the same 28 days:
| Page | Bot pageviews |
|---|---|
| Manfrotto PIXI EVO review | 15 |
| Monitor arm buying guide | 15 |
| Logitech MX Master 3 review | 14 |
| Sony NW-A105 review | 11 |
| microSD buying guide | 10 |
| HDMI cable buying guide | 9 |
Those were exactly the pages that looked like my most-read. With the profile filtered out, the top post on the site is the Cloudflare Workers cron post, which the bot never visited. The reviews still have real readers, but they were never the top of the site.
The affiliate links stay on those reviews, with smaller expectations.
The bot is not only in Singapore, either. In the same week, 47 beacons from the United States were Chrome 122, all inside a single UTC hour. A country filter takes out the biggest block, but it does not make the rest clean.
What I changed, and what I left
I did not block Singapore. The traffic costs nothing beyond inflated counts, its scanning is already stopped by the path rule, and a country block would also turn away real readers there. Blocking is the right tool for protecting the origin and the wrong one for cleaning a metric, the same split I ended up with after a month of WAF rules.
GA4 cannot filter this for me. It drops bots on its known list automatically, and that list cannot be seen or extended. Its own data filters cover internal and developer traffic only.
So the fix lives in how I read the numbers:
- Read
engagedSessionsinstead of sessions. The bot produced 1 engaged session in 210. - Exclude
screenResolution = 1280x1200from Singapore when ranking pages, and recheck the profile now and then. The resolution is the bot’s choice, so it can change. - Before acting on a “top page,” check where its views come from.
The last one is the step I almost skipped. A page that looked like my best earner was mostly one machine’s routine, and ten minutes in the country report was enough to see it.
FAQ
Why does GA4 show so many direct visits from Singapore?
On my site it was an automated browser, not readers. 210 sessions in 28 days produced one engaged session and zero seconds of engagement, 201 of them reported the same 1280x1200 screen, and each landed on a single page and left. Singapore hosts a lot of cloud data centers, so a crawler that runs JavaScript there fires your GA4 tag the way a visitor would.
Doesn’t GA4 exclude bots automatically?
Only known ones. GA4 drops traffic from bots and spiders on its known list, and you cannot see or extend that list. A headless Chrome that sends a normal browser user agent is not on it, so it is recorded as a session. GA4’s own data filters only cover internal and developer traffic, which you define by IP or parameter, so they do not help with someone else’s crawler.
Can I see the ASN of a visitor on Cloudflare’s free plan?
Not through the GraphQL Analytics API. Asking httpRequestsAdaptiveGroups for clientAsn on a free zone returns an authz error saying the zone does not have access to the field. The user agent, path, status and hour are available, and the Web Analytics beacon at /cdn-cgi/rum tells you which of those user agents actually executed JavaScript.
Should I block Singapore at the WAF?
I didn’t. The traffic does no damage beyond inflating counts, its credential-scanning requests were already 403s from an existing path rule, and a country block would also turn away the occasional real reader there. I exclude it when reading the numbers instead: look at engaged sessions, and drop the 1280x1200 profile when ranking pages.