Skip to main content

Methodology

How myping measures the internet — the engine, what we record, and what leaves your device.

How myping measures speed

myping uses @cloudflare/speedtest (MIT-licensed) as its sole measurement engine. The SDK runs in your browser and exchanges binary payloads with speed.cloudflare.com/__down and /__up over HTTPS — the same endpoints Cloudflare ships with their own speed test.

Latency and jitter are measured first as round-trip time on small packets; the median is the reported ping and the standard deviation is the reported jitter. Download and upload are measured through adaptive request ladders: small warmup requests first, then larger payloads when the connection is fast enough to need them. Upload uses a shorter ladder so slower upstream links do not dominate the test runtime. The gauge in the hero animates live SDK bandwidth points; the result card commits the SDK’s final aggregate reading.

Cloudflare’s engineering blog describes the exact protocol. We don’t reimplement it — we drive the SDK they maintain.

While download and upload are running, the SDK also issues latency probes through the same connection. These “loaded latency” readings — the round-trip time under full bandwidth load — are how we surface bufferbloat: a connection can have low unloaded ping but become unusable under load if a router upstream lacks queue management. The Gaming and Video-call ratings on the result page factor in loaded latency for that reason — anything over ~150ms of latency under load drops the rating one tier; over ~300ms drops it two.

Why we don’t let you pick a server

Cloudflare’s network is anycast: a single hostname (speed.cloudflare.com) resolves to one of hundreds of colos worldwide, and your ISP’s BGP routing decides which one your packets reach. The browser is downstream of that decision.

Picking a specific colo from the browser is not technically possible:

  • The Host header is on the WHATWG Fetch forbidden-header list, so a page cannot rewrite it to point at a colo-specific hostname.
  • Browsers cannot skip TLS certificate validation per request, so we can’t hit a colo’s IP directly with the right SNI.
  • BGP termination happens upstream of the user agent — the colo is decided before the request reaches Cloudflare.

Cloudflare’s team confirmed this in cloudflare/speedtest#33. Any picker we shipped would be a UI fiction — we’d let you select “LHR” and your packets would still go wherever BGP took them. We don’t do that.

Instead, after the test completes we read the actual colo your traffic terminated in from speed.cloudflare.com/cdn-cgi/trace and surface it in the colo info panel beside your result. That’s the truth-in-data anchor: we record where your test ran, not where we wished it ran.

Adaptive request ladder

myping does not expose a mode selector in the current browser UI. Every new homepage run uses one standard Cloudflare-backed profile, while older stored results that were stamped as Multi or Single remain readable in history.

The standard profile uses the same Cloudflare endpoints and follows an adaptive byte ladder: short warmups at 100 KB, mid-size download measurements at 1 MB, 10 MB, and 25 MB, then larger download requests up to 250 MB when the SDK needs more signal for a fast link. Upload stops at smaller tiers so the upload phase stays proportionate.

Legacy mode preferences in localStorage are ignored for new runs and coerced back to the standard Single value. API routes still accept both mode strings so old clients and historical filters do not break.

Privacy and abuse protection

Before you run a test, the browser asks our API for a session token. The token is a short payload signed server-side with HMAC-SHA-256, bound to the colo IATA your traffic just routed to and the standard test mode. It’s valid for two minutes, one test. The results endpoint refuses anything else.

When a result is persisted we record only salted hashes of your IP and User-Agent via the hashIpFromHeaders and hashUserAgent helpers. The raw values never enter the database. The hashes let us deduplicate abuse and reconstruct rough device class without storing identifiers.

Full data-handling details live on the privacy page.

Anonymous result history

Your test history on this device is keyed by the myping_visitor cookie — an HttpOnly identifier set the first time you load the site. There’s no account, no email, no login. The cookie is the entire story.

If your browser blocks cookies, no history is kept on this device. Each individual test still works, but the “Recent results” list will stay empty — and a shareable result link is still generated, since it lives on its own opaque public ID.

We tag each result with a UA-derived connectionClass (Fixed or Mobile) used by the global index Fixed/Mobile tabs. UA classification is a heuristic — accuracy is roughly 70–85%, never authoritative — and is intentionally low-resolution so it can’t be used to fingerprint a specific device.

What we share with Cloudflare and Measurement Lab

All measurement traffic transits Cloudflare — your browser is talking to speed.cloudflare.com, not myping’s servers, during the test itself. Cloudflare sees the IP, the colo, and the bandwidth/latency samples, governed by their privacy policy.

Per Cloudflare’s October 2025 announcement, Cloudflare aggregates measurement metadata from their speed-test infrastructure and contributes it to Measurement Lab’s open dataset on Google BigQuery. Because we drive the same SDK against the same endpoints, our test traffic is part of that pipeline.

We do not share our own anonymous result history beyond that aggregation path. The hashed-IP, hashed-UA, visitor-cookie history described above stays on myping’s database; it does not get re-uploaded to BigQuery, sold, or syndicated.

For developers

When DATABASE_URL is unset (typical for local development), results land in an in-memory map on globalThis.__mypingResults. That store is process-local and is wiped on every server restart — useful for clicking through the UI, not durable in any sense. Production always has DATABASE_URL set and writes to Postgres via Prisma.

The SDK, fonts, icons, and other dependencies are listed on the attributions page once it ships.