← Writing

474 requests from the same iPhone, from 38 different countries

What a day of raw server logs looks like in 2026, and why nothing you have is watching it.


I pulled a day of access logs off a small Bulgarian site — a youth centre, maybe a few hundred human visitors — and read all of it. Not the analytics dashboard. The raw log.

Here is what a boring site's boring day looks like now.

The visitors that announce themselves

  70  bingbot/2.0
  33  Googlebot/2.1
  25  Applebot/0.1
  22  OAI-SearchBot/1.0

All well-behaved. OAI-SearchBot asked for robots.txt first and got a 404, because the site doesn't have one — a small thing that I'll come back to.

This group is the easy part. They say who they are, they respect the rules if you write any, and every bot-detection tool in existence can see them.

The 474

 474  Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X)
      AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3
      Mobile/15E148 Safari/604.1

Byte-for-byte identical. Across dozens of source addresses: 43.166.x, 43.159.x, 119.28.x, 139.155.x, 152.32.x — Tencent Cloud, mostly, with a few neighbours.

Three things are wrong with that picture at once.

iOS 13.2.3 shipped in 2019. A handful of people still run it. Not 474 requests' worth on a municipal site in Bulgaria.

Real phones don't come from datacenters. Tencent Cloud rents servers; it doesn't sell mobile plans in the Balkans.

And real people don't share a fingerprint. Two users on identical iPhones with identical iOS builds would produce slightly different header sets — different languages, different Accept orderings. These were identical.

One program. Many rented addresses. One stale user-agent string copied across all of them.

It appeared in no bot report anywhere, because it never claimed to be a bot.

The one wearing a 2016 browser

35.236.234.99  "GET /wp-includes/wlwmanifest.xml"          404
35.236.234.99  "GET /blog/wp-includes/wlwmanifest.xml"     404
35.236.234.99  "GET /wordpress/wp-includes/wlwmanifest.xml" 404
35.236.234.99  "GET /shop/wp-includes/wlwmanifest.xml"     404
   ... twenty more paths, three seconds apart
UA: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/78.0.3904.108

Chrome 78 is from late 2019. The address is Google Cloud. The paths are a standard WordPress reconnaissance sweep — wlwmanifest.xml reveals a WordPress install, and its version, which tells an attacker which exploits to try.

Then, separately, 1,221 requests to /wp-admin/install.php?step=1, spread through the day and arriving via Cloudflare addresses. That endpoint is only reachable on a WordPress site that's been uploaded but never configured — the one moment when anyone can claim the admin account. Someone is checking, at scale, whether today is that moment.

The site isn't WordPress. Every one of them got a 404.

What the host was blocking

The interesting part isn't what got through. It's what didn't.

I ran the same request nine times against the site, each time wearing a different well-known crawler's name and nothing else — no signature, exactly what a scraper borrowing a name would send.

IdentityResponse
Normal browser200, 30,684 bytes
ChatGPT-User200, identical
Claude-User200, identical
Google-Agent200, identical
PerplexityBot200, identical
OAI-SearchBot200, identical
GPTBot200, identical
ClaudeBot406, 289 bytes
Bytespider406, 289 bytes
Googlebot200, identical

Two blocked out of nine. I hadn't configured either. It's a ModSecurity rule from the hosting provider, applied to a list they maintain.

Consider what that rule actually does. It reads the name in the request and refuses it. So:

  • The real ClaudeBot — Anthropic's crawler, doing its documented job — gets a 406.
  • A scraper that types ClaudeBot into its user-agent also gets a 406, then changes one word and gets a 200.
  • GPTBot, the largest training crawler, walks straight in, because it isn't on the list.

The site owner didn't choose any of this and has no idea it's happening.

The pattern

Sort the day's traffic by how visible it is:

Announces itself, easily seen, mostly harmless. Googlebot, bingbot, Applebot, OAI-SearchBot. These are what people mean when they say "bot traffic", and they're the least of it.

Announces itself falsely, easily seen, sometimes blocked. Whatever picks a famous name to get past filters. The name-based rules catch some of these — and catch the real thing along with them, since a name is all they look at.

Announces nothing, invisible to every name-based tool. The 474. The vulnerability sweeps. The one that came to my canary. This group is the largest and the only one worth worrying about, and it's the one nothing on that server was watching.

Why your analytics show none of this

Google Analytics runs in the browser. It needs the visitor to execute JavaScript, load an external script, and not block it.

Crawlers don't execute JavaScript. Vulnerability scanners don't. The 474 didn't.

So the entire category is missing from analytics — not filtered out, never observed. If you've looked at your dashboard and concluded you don't have an AI crawler problem, you've learned nothing about whether you do.

The only place this is visible is the server, which is also the only place a request has to arrive regardless of what it's willing to run.

What can actually be done

Name-based blocking, which is what most hosts do, stops the honest and inconveniences nobody else. Worth understanding as roughly decorative.

Behavioural signals — datacenter origin, missing browser headers, one fingerprint across many addresses — catch the crude ones. The 474 score in the high seventies on that basis; so does the Chrome-78 sweep. But a careful headless browser through a residential proxy scores in the teens, and I've tested that with a real one. This layer has a ceiling and it is not high.

Cryptographic signatures actually settle it. Web Bot Auth has agents sign each request with a key whose public half is published at a known address. A forged GPTBot can't produce a valid signature. Adoption is early — Google signs some agent traffic, most operators don't sign at all — but it's the only approach that doesn't degrade into guessing.

Published IP ranges are the useful middle ground today. OpenAI publishes gptbot.json, searchbot.json, chatgpt-user.json. Google publishes files per crawler category. Match the claimed name against the operator's own list and you have a real answer for a large share of your traffic, right now. Anthropic publishes nothing, so ClaudeBot stays unverifiable either way.

None of these help with something that claims nothing at all. You can only check a claim that was made.

The small thing worth doing today

That 404 on robots.txt bothers me more than the 474.

OAI-SearchBot asked what the rules were. The site had no answer. Neither did it for Googlebot, Applebot, or any of the others that check.

robots.txt doesn't stop a bad actor and was never meant to. What it does is tell the well-behaved majority what you want — and right now, most sites are saying nothing at all, then being surprised by what happens.

It's ten minutes, and it's the only part of this you fully control.


Logs from a real site, anonymised where it mattered. I built the tooling I used here — a free scanner that shows whether your site distinguishes a real agent from one wearing its name, at crawlprint.com. It shows you what can be known, and says where it can't.

474 requests from the same iPhone, from 38 different countries · Crawlprint · Crawlprint