Method · how to read the observatory

What the numbers mean

A request is one HTTP fetch that reached the edge, and the count is one log record per fetch. That is the whole definition. Everything below is the consequences of it — which numbers are exact, which are estimates, how big the error is, and what any of it is next to the size of the web.

What counts as one request

Every single thing a client asks for is one request: an HTML page, an API call, one JavaScript chunk, one image, the favicon, robots.txt, a redirect, an analytics beacon. It is not a visit and not a page view. Nobody “visits” in these numbers; things get fetched.

A request earns a place in the count by reaching Vercel's edge. What happens next decides which of two very different streams it lands in, and that difference is the single most important thing on this page.

Figure 1

One request, end to end

A clientperson, or fleetVercel's edgenearest cityThe firewalllooks first, atevery requestRefused403 or 429, nothing served1 firewall recordall of them keptAllowedserved, and logged as one oflambda · static · redirect · edge · external1 in 10 keptThe drain boxone line eachhourly file → rollup→ bots.json → this pageNever counted anywhere:DNS lookups · connections that failedtraffic Vercel's DDoS layer drops firstpreview and *.vercel.app hostnames

The firewall looks first. If it says no, Vercel answers 403 or 429 and writes one firewall record — and all of those are drained. If it says yes, the site serves the request and writes one record of five kinds — and only one in ten of those is drained, head-sampled on a hash of the request ID. That asymmetry is deliberate: it holds the log bill near $2 a month instead of $34, and it costs us nothing on the side of the numbers we most want to be exact.

Why a person and a bot are not comparable

One person loading one page of aboutus.com produces something like five to fifteen requests: the page itself, several script and style chunks, the favicon, the analytics beacon. One bot probing one URL produces exactly one. So a raw share of requests systematically overstates humans — every person arrives pre-multiplied by a dozen, and every probe arrives as one.

That is what the per address-hour column on the observatory is for. It counts how many requests came from each source address within each hour. A person browsing shows up as a burst on one address. A fleet renting a thousand addresses shows one request on each, and lands at about 1.0 — which is a shape no real person has.

Figure 2

A person and a fleet, the same twelve requests, opposite shapes

One person, one page12 requests · 1 addressthe HTML pageseven script and style chunkstwo images, the faviconone analytics beacon12.0 per address-hourA fleet, one probe each12 requests · 12 addresses.40.47.54.61.68.75.82.89.96.103.110.117twelve rented addresses, one request apiece,all asking for /index.php1.0 per address-hour

Identical totals, and nothing else in common. This is why the observatory ranks networks by requests per address-hour rather than by request count: it is the measure that separates a residential ISP with real customers from a hosting network running browser-shaped software. It is also why we key rate limits to a TLS fingerprint rather than an address — on shared carrier-grade NAT, a per-address limit hits the person and misses the fleet.

Humans 47%
Bots 44%
AI

Requests for HTML documents only, worldwide: 47% human, 44% ordinary bots, 4.2% AI crawlers. Cloudflare Radar, 2025 Year in Review, 2025-12-02. HTML is the one slice where a request and a page nearly mean the same thing, which is why this is the split everybody quotes — and why it says nothing about total request traffic, where one page load is a dozen requests and one probe is one.

Refused, and its three shapes

Refused means the firewall answered instead of the site. It comes in three kinds, and collapsing them loses the thing worth knowing. Denied is a 403: the client got nothing. Challenged is an interstitial asking the client to prove it is a browser. Rate-limited is a 429 for going over the cap on a TLS fingerprint.

The trap is that a challenged person is counted twice — once here, when the interstitial is served, and again a second later as the handful of allowed requests that make up the real page. So when most of “refused” is challenges rather than denials, the headline number is softer than it sounds. Denials are the requests that truly got nothing.

Figure 3

What the refused number is actually made of

REFUSED IN THE LAST 24 HOURS — 5,512,334 REQUESTSChallenged5,478,311 · 99%Denied: 34,023 (0.62%) — got nothing at allDenied is the honest count of requests that got nothing.Challenged is drawn hatched because it is a softer refusal — and a person who passesthe challenge is counted twice: once refused here, then again as the allowed requests a second later.

Live, from the last 24 hours. Both segments are the same hue because both are refusals; the hatch marks the softer one. Every count here comes from the 100% firewall stream, so unlike almost everything else on the observatory these are exact request counts, not estimates.

Allowed, and the ten-times

Everything the firewall lets through is served and logged as one of five record kinds: lambda for a server-rendered page or API route, static for a cached or static file, plus redirect, edge and external for the analytics beacons. The baseline drain keeps one record in ten. The rollup multiplies by ten to get back to a request count.

Figure 4

Ten requests in, one record kept, ten back out

TEN REQUESTS ARE SERVEDkeptthe drain keeps one line in ten, chosen by a hash of the request IDTHE ROLLUP MULTIPLIES IT BACK× 10 = 10— right on average, and wrong on any particular hour by an amount you can compute.

Head sampling is a deterministic hash of the request ID, not a coin flip — but for any slice chosen without reference to that hash (a site, an hour, a named crawler) it behaves exactly like one. Which means the estimate is unbiased, and wrong on any particular bucket by an amount you can calculate. That amount is the next section.

This is why every allowed and total figure on the observatory is printed with a tilde, and why the feed also publishes the unscaled *_raw counts next to the scaled ones. The raw count is the only honest denominator for an error bar.

How exact this really is

“One line equals one request” is an assumption, and it is checkable: every drained record carries Vercel's request ID, so counting distinct IDs and dividing says exactly how far off it is. Here is a whole hour, audited.

Log records per request, by record kind, for the hour beginning 2026-09-03T22:00Z
Record kindWhat it meansRecordsDistinct requestsRecords per request
firewallthe firewall answered it: denied, challenged or rate-limited67,20467,2041.000
lambdaa server-rendered page or an API route ran3,6043,4471.046
statica cached or static file was served3583581.000
redirectthe edge answered with a redirect2292291.000
edgemiddleware or an edge function ran1611611.000
externalan analytics beacon1001001.000
allplus 43 requests that produced records of more than one kind71,65671,4561.003

One whole hour of raw drain records, the hour beginning 2026-09-03T22:00Z. Counting distinct request IDs is the only way to tell a log line from a request. Overall the records run 0.28% above the true request count.

Three things fall out of that. Firewall records are one per request — 67,204 records for 67,204 requests, dead level, so refused counts are true request counts. Lambda records are not: a function that logs twice during one request writes two lines, so function traffic reads about 5% high. And a few requests produce records of more than one kind — a redirect line and a lambda line for the same fetch — adding a little more on the allowed side.

Netted out, the records for that hour ran 0.28% above the true request count. So the honest statement of the whole thing is: the refused numbers are exact, and the allowed and total numbers are ten-times estimates of a record count that sits a couple of percent above true requests, with sampling noise on top. Vercel's own billing counter is lower than our record count for precisely this reason.

The error you should attach

Scaling a sample up by ten is exactly right on average and wrong on any given bucket. How wrong has a clean answer. Writing n for the number of records the drain actually kept and p for the sample rate, the relative standard error of the scaled count is √((1−p)/n) — which at a 1-in-10 sample is almost exactly one over the square root of the records you kept.

The striking part is what is not in that formula: the size of the true number. A bucket is uncertain because it is thinly sampled, never because it is small or large. A crawler with twenty kept records is ±21% whether those twenty represent two hundred requests or two hundred thousand.

Figure 5

Certainty is bought with kept records, and only with kept records

0%25%50%75%100%1101001.0k10k100k1.0M10Mrecords the drain actually kept, for whatever you are countingdns.pizza, one day±0.4%everything, one week±0.1%sebot-wa, one week±24%

One standard error against the number of records the drain kept, logarithmic. The marked points are real buckets from the live feed. The practical reading: site and week totals are solid to a fraction of a percent, and the long tail of rarely-seen crawlers is soft — which is why the observatory sorts the named-bot table by volume and does not invite you to read the bottom of it too closely.

Records the drain must keep to hold the relative error under a target
To be sure withinthe drain must have keptwhich means roughly
± 50%4 records40 real requests
± 20%23 records230 real requests
± 10%90 records900 real requests
± 5%360 records3,600 real requests
± 1%9,000 records90,000 real requests

One standard error, at a 1-in-10 sample. Refused counts are not sampled at all and do not appear here.

How many of these exist at a single moment

A request is not an event with no duration — it is alive for a while. So “how many exist right now” is a real question with a real answer, and it is not the same question as how many happen per second. The bridge is Little's Law: the number in flight is the arrival rate multiplied by how long each one lives. Nothing more.

Cloudflare answers about 81 million HTTP requests a second on average and sits in front of roughly a fifth of the web, which puts the whole web on the order of 405 million requests a second. A request lives at the edge for somewhere between about 50 milliseconds and half a second. Multiply: at any instant, somewhere between 20 million and 203 million HTTP requests are in flight across the Internet, call it 81 million.

Figure 6

One pixel per request in flight, right now

ONE PIXEL = ONE REQUEST IN FLIGHT, RIGHT NOW~81 million worldwideone pixel each, that is about 10 4K screens of them23 oursdrawn actual size,one pixel eachThe rectangle is not to scale — at one pixel per request it would need to be 10 times a 4K screen.The green dots are. That is the honest ratio: about one in 3.5 million.

Give every in-flight request in the world one pixel and they fill about 10 4K screens. Give ours the same pixel and they fit in 23 of them, drawn actual size on the right. Both halves of that come from the same arithmetic; only the rectangle is not to scale, because it cannot be.

The band matters more than the midpoint here, and it is honest about where the softness is: the request lifetime is an assumption, and Cloudflare's “one in five sites” is a share of sites being used as a stand-in for a share of requests. Treat the result as an order of magnitude — tens of millions in flight, not hundreds of thousands and not billions.

How many happen in a day

405 million a second is about 35 trillion HTTP requests a day across the Internet. For scale in the other direction: the entire DNS root server system — all thirteen identities, the thing the whole naming system hangs from — answers around 130 billion queries a day, which is under half a percent of that. The web asks for far more than it looks up.

Figure 7

From one request to a day of the Internet

110010k1.0M100M10B1.0Trequests — logarithmic, each gridline is a hundredfoldOne bot probing one URL1One person loading one page12Our five sites, per second117Our five sites, per day~10MCloudflare, per second (average)81MCloudflare, per second (peak)129MThe whole web, per second~405MDNS root servers, per day130BThe whole web, per day~35T

Logarithmic: every gridline is a hundredfold. Our own traffic is in orange. A useful thing to notice is how small the interesting range is — a single page load and the entire web are separated by twelve orders of magnitude, and almost everything anyone argues about lives in the middle four.

Which puts our own numbers in their place. Our five sites saw about 10 million requests in the last 24 hours and refused 5,512,334 of them. Everything we saw in a whole day passes across the world's servers in about 25 milliseconds.

Figure 8

Our whole day, on the world's clock

ONE SECOND OF THE WORLD'S WEB TRAFFIC~405 million requestseverything our five sites saw in a whole day — 25 ms~10 million requests, of which 5,512,334 were refusedScale is exact: the orange sliver really is 2.5028% of the bar.

The bar is one second of the world's web traffic and the orange sliver is our entire day, drawn to scale. This is not self-deprecation, it is calibration: it is why an attack that would be invisible in any global statistic can still be the largest thing that has ever happened to these sites, and why a firewall rule tuned on somebody else's scale will be wrong on ours.

Every published figure, with its source

Two kinds of number appear on this page and they are never mixed. Measured means somebody counted it and published it; each one below carries the source and the date the figure speaks for, not the date we read it. Derived means we did arithmetic on those, and the assumption is stated in words next to the result so you can disagree with the assumption rather than the number.

Published figures this page rests on, with sources and the date each speaks for
WhatFigureSourceSpeaks for
HTTP requests Cloudflare answers, per second, on average81,000,000Cloudflare Radar, 2025 Year in Review2025-12-02
The same at peak129,000,000Cloudflare Radar, 2025 Year in Review2025-12-02
Share of the web sitting behind Cloudflare
A share of sites, not a share of requests. We use it as a stand-in for the request share, which is the weakest step in the estimate below.
20%Cloudflare, “powers 1 in 5 sites on the Internet”2026-09-04
DNS queries Cloudflare answers, per second
Authoritative and resolver combined.
67,000,000Cloudflare Radar, 2025 Year in Review2025-12-02
DNS queries the thirteen root server identities answer, per day
Huston's figure is “more than 130B queries per day at the start of 2025”, up 40% from about 90B at the start of 2023. We carry the lower bound.
130,000,000,000Geoff Huston, “The root of the DNS”, APNIC2025-01-01
Websites that answered Netcraft's survey1,494,915,628Netcraft Web Server Survey, July 20262026-07-01
Domains behind them305,348,459Netcraft Web Server Survey, July 20262026-07-01
Web-facing computers behind those
A billion and a half sites run on fifteen million machines. Most of the web is one server wearing many names.
14,772,048Netcraft Web Server Survey, July 20262026-07-01

The two derived figures are the world's request rate (405 million a second — Cloudflare's average divided by Cloudflare's share) and the in-flight count (81 million — that rate times an assumed 200-millisecond lifetime). Both are order-of-magnitude claims and are marked as such wherever they appear. Our own counts are not estimates of anybody else's traffic; they are measurements of ours, with the exactness set out above.

A note on what is never counted: DNS lookups, connections that failed before an HTTP request existed, traffic Vercel's own DDoS layer drops before the firewall sees it, and anything on a preview or *.vercel.app hostname. There is a whole layer of refused traffic below our floor, and we do not have it.

Glossary of every field

Every number the observatory and the public feed publish, what it counts, and whether it is exact.

Every number the observatory publishes, what it means, and whether it is exact
WordField in the feedWhat it countsHow solid
Request—One HTTP fetch that reached Vercel's edge for one of our production hostnames. Not a visit, not a page view. The favicon is a request.exact
RefusedblockedThe firewall answered instead of the site: denied, challenged or rate-limited. Counted from the 100% firewall stream, so it is a true request count.exact
DenieddeniedA 403. The client got nothing. This is the number to use when you mean 'we turned it away'.exact
ChallengedchallengedAn interstitial asking the client to prove it is a browser. A person who passes is counted here once and then again in allowed.exact
Rate-limitedrate_limitedA 429: over the cap for that TLS fingerprint. Usually a rounding error next to the other two.exact
AllowedallowedEverything the firewall let through, served and logged. Recovered from a 1-in-10 sample by multiplying by ten, so it carries sampling error and reads a few percent high.estimate
Raw*_rawThe unscaled count of records the drain actually kept. The honest denominator for any error bar; the reason the feed publishes it at all.exact
Requests (total)requestsRefused plus allowed. Exact on one side, a ten-times estimate on the other, which is why the page prints it with a tilde.estimate
Per address-hourhits_per_ip_hourRequests divided by the number of distinct source addresses seen in each hour, summed. A person browsing sits well above 1; a fleet on rented addresses sits at about 1.derived
NetworkasnThe autonomous system the source address belonged to. The address is resolved to an ASN and then discarded — we never store or publish IPs.exact
BypassbypassRequests the firewall deliberately waved through on a system rule, mostly Vercel's own infrastructure.exact

The feed these all come from is public at n1.snowc.one/public/bots.json. Back to the observatory, or on to the daily zone census. Live counts on this page are from the feed generated 2026-10-19 01:25 UTC.