← How It Works

Probes — how howismywifi measures your connection

One capture runs two kinds of probe at once. Continuous probes (ICMP, UDP, QUIC, DNS…) tick the whole time — lightweight, meant to overlap everything and catch every latency spike and outage. Scheduled probes (speed tests, video quality) are bandwidth-intensive, so they run one at a time and never contend for the link. The phone apps and the desktop tool schedule them differently; this page is generated from both schedules — every number below is what the apps actually run.

At a glance — what each capture length runs

Scheduled probes per capture, one square per test. Speed tests are our own test plus the Akamai and Netflix cross-checks; video tests are our hosted stream plus YouTube. LEO covers any low-latency link (Starlink, ground Wi-Fi); GEO satellite tests take about 3× longer, so desktop fits fewer of them. The Windows app moves to this schedule in its next release.

Capture Phone · LEOPhone · GEODesktop · LEODesktop · GEO

Schedule explorer

Pick a capture length to see when each scheduled probe runs. Continuous probes run underneath the whole time and aren't drawn.

Capture length

Desktop scheduler in detail

The desktop tool (macOS, Windows, CLI) groups its scheduled probes into rounds, spaced across a 30-minute cycle by the wave scheduler (scheduler.py).

Link type
Profile

30-minute measurement cycle

Two tiers, moderated differently

Scheduled probes split into flood (speed tests — they saturate the link, so they're kept sparse and spaced to leave the latency probes clean unloaded windows) and user interaction (video / CDN traffic — dense is fine because it mimics a real passenger and doesn't saturate). The link tier is the continuous probes running underneath everything.

What a 5 / 10 / 30-minute capture gets

Whenever a capture ends, has it collected the most useful set? Short captures drop rounds from the end, so a longer capture is a strict superset. GEO probes cost more, so the same capture length collects fewer rounds on satellite than on a fast terrestrial link.

CaptureGEO satelliteTerrestrial / LEO

Intensity — how hard the tool works the link

Two independent axes. The profile (active / passive) retunes the continuous probes; the schedule repaces the heavy scheduled rounds. The CLI drives both directly (wifi-capture --profile / --schedule). The macOS app folds them into one three-stop Light / Normal / Heavy control — Light is --profile passive, Normal is the default, Heavy is --schedule intensive — available on captures of 15 minutes or longer, below which every stop runs Normal (shorter captures truncate a single cycle, so Heavy would have nothing to add). more-video and budget-video stay CLI-only: both are video-first and need --allow-metered. The iPhone and Android apps have no profile/schedule axes; Continuous captures offer a two-stop Normal / Heavy control instead. Normal runs 7 of our speed tests, 2 Akamai and 2 Netflix cross-checks per 30-minute cycle; Heavy adds 3 more of our speed tests to the same cycle (20 an hour, matching desktop Heavy). Heavy pauses its extra tests while the phone is hot or in Low Power Mode, warns on battery, asks first on cellular, and returns to Normal after 6 hours. Duty cycle is the share of each cycle spent actually probing — the saturation number. ST counts every throughput test, our own and Akamai both (the Akamai runs are a deliberate CDN cross-check, not overhead).

Video probes
IntensityFlagCycleProbe time Gap / roundDuty cycle3h capture5h capture

The probes in detail

Scheduled probes — bandwidth-intensive, run in rounds

Speed test (our server)

Download and upload speed via four parallel HTTP transfers — it saturates the link (like Ookla) to measure true capacity. It is our own test, not Cloudflare’s: it runs against our speed-test server on tcp.howismywifi.net (on iPhone and Android, alternating with an HTTP/3 twin on quic.howismywifi.com), and falls back to speed.cloudflare.com, then to Akamai when shared satellite NAT IPs trip per-IP rate limits. From iPhone build 116, our test runs TCP only on GEO links. (The Windows app moves to these hosts in its next release; until then it tests against our older server.) On GEO the test runs longer (~12 s) so the high-latency link has time to reach steady state. The reported speed is the sustained rate after that ramp — the median of each round's post-ramp steady-state throughput, not a whole-transfer average (which would read low on high-latency links still climbing the ramp).

Akamai speed test

A second speed test on a different CDN (dash.akamaized.net). Running it alongside the main speed test cross-validates capacity and exposes per-CDN shaping — an airline may cap one CDN while another runs clear on the same link, which a single backend would never reveal.

Netflix OCA

Download throughput from the same Netflix Open Connect servers that serve real Netflix video, mapped to a streaming resolution tier — so it answers a question users actually have: would Netflix play in HD here? It surfaces per-CDN rate limiting directly: Netflix may be capped at 2.3 Mbps on GEO while the main speed test sees 10+ Mbps on the same link. Successive Netflix slots in a capture alternate between one connection and four to the same server, starting on a random one, which tells a per-connection video cap from a whole-device one. The Netflix number and resolution tier come from the one-connection runs; a short capture whose only Netflix run used four connections shows that value labelled "4 connections", with no resolution tier. Passive, data-capped and on-demand runs always use one connection.

YouTube segment

yt-dlp resolves a video's DASH/HLS manifest, then downloads four real media segments and times each — reporting achievable bitrate, time-to-first-byte, CDN host, codec, and a recommended resolution. Fast and dependency-free, it runs on every YouTube slot (no Chromium needed) and is the data behind the "Recommended quality" chip on the results page.

YouTube ABR

When Chromium is installed, this launches a real headless YouTube player in two phases: AUTO (let YouTube's own algorithm pick the quality on this link) and FORCED (ask for the resolution the segment probe said should work, and see what actually happens). It captures real startup latency no throughput test can — and when the segment probe says 1080p but ABR locks to 480p, the platform is throttling the player itself.

YouTube Shorts

Drives a real headless Shorts feed — swiping through short-form clips and reading comments — to measure the rebuffering and quality a passenger sees on the format they actually use most. Two flavors: a standard ~3-min session, and a Deep ~6-min round ( on GEO; varying swipe cadence, comments read twice) that stress-tests sustained short-form playback. The stall-based quality verdict is withheld until it's validated on a real flight.

Video playback (our stream)

Plays a real adaptive video stream from our own server (video.howismywifi.net): HLS in 2-second segments, five H.264 renditions from 240p at 300 kbps to 1080p at 5 Mbps (a bitrate-matched VP9 set on desktop). Startup is timed on our own clock, from pressing play to playback actually moving (phones show a first frame earlier, then wait for enough video), and split into its round trips (DNS, connect, TLS, the two playlists, the init and first segments, then the first frame), so a slow GEO start reads as so many round trips × RTT rather than one opaque number. It also records stalls, buffer ahead, dropped frames, per-segment speed and time-to-first-byte, the share of segments that arrived faster than real time, and whether each request came from the edge cache (a first request at a rarely used Cloudflare site reads slow). Each run opens with a forced phase: a cold start on a single rendition that our server sets (1080p / 5 Mbps), identical on every device, so GEO, LEO and terrestrial compare like with like. The clip comes from a short curated pack our server publishes. The app rotates through it, the two phases of a run play different clips, and every row records which clip played. Each clip is encoded to the same bitrate targets before it joins the pack, so clip-to-clip differences stay within a tested bound, and results are audited per clip. The forced phase runs 20 s and the ABR phase 24 s. The server can tune both within fixed limits, and results pool only across runs at the same forced quality and length. An ABR phase, where the player picks the quality (capped at 1080p), follows on desktop in the full-video and Deep rounds, right beside the YouTube player test so the two compare on the same link; desktop also runs a forced phase alone in its first round and in Var (4 forced and 2 ABR phases per 30 minutes, the first about 1–2 minutes in). The phone apps run one forced phase in Lightning and in Standard (first, as soon as the app can show it), and one after each of the 7 speed tests in a 30-minute Extended or Continuous cycle, one of them a full run with the ABR phase. On iPhone (iOS 26+, build 113+), a phase that comes due while the app is in the background waits for the app to come back, and is skipped (skipped=backgrounded) only once it runs out of time, so a capture that runs in a pocket records no video. The ABR phase is reported per platform only, because iPhone, Android and desktop players adapt differently. It also runs when the forced phase failed to start (no picture in time, or a playlist or player error), so its start rate is what a passenger pressing play would see. It is still skipped on a metered connection, after a codec failure or a phase that hit its data cap, and when video is switched off. Older app builds ran it only after a forced phase that didn't fail, so their ABR results lean optimistic on a bad link; rows from builds that always try it carry +aalways in player_version.

Live on iPhone (build 109+; the round-trip breakdown needs iOS 18+), Mac (0.8.12+) and Android (0.1.16+); Windows from its next release. The curated pack and the 1080p forced phase arrive with the command-line tool 0.1.1+. The player buffers ahead the way a real streaming app does, rather than being held back to save data, so on a fast link the forced phase fetches up to about 37.5 MB (the whole clip) and a full desktop run about 75 MB. Each phase is hard-capped at 45 MB. Metered links run only the forced phase. Our server isn't a known streaming service, so rules that target streaming services don't single it out: this measures what the link can do for video, and it can read better than Netflix or YouTube would on a link that throttles them. It's a measurement, not a quality score. Videos: An Ocean in Motion and Perpetual Ocean, NASA Goddard Scientific Visualization Studio (public domain); Thermonuclear Art: The Sun in UHD, NASA Goddard / SDO (public domain); Sol Levante, Netflix with Production I.G | opencontent.netflix.com, licensed CC BY 4.0.

Continuous probes — lightweight, run the whole capture

ICMP ping

Base round-trip time and packet loss — the most fundamental measurement, an echo request/reply with no application-layer overhead. A gateway ping runs alongside to separate a local Wi-Fi problem from an internet-path one, and two internet targets guard against one being rate-limited.

UDP / DNS

A real UDP round-trip via a minimal DNS query — a second latency and loss signal independent of ICMP. PEPs don't repair UDP loss, so this stays honest on GEO where TCP and ICMP loss are masked. UDP loss, RTT, and jitter feed the Voice MOS estimate.

TCP connect

Times a TCP three-way handshake to port 443. This is the key detector for TCP Performance Enhancing Proxies: airlines deploy transparent proxies that answer with a local SYN-ACK, making TCP look far faster than the real satellite round-trip. A large ICMP-vs-TCP gap is the PEP signature.

QUIC handshake

A full UDP + TLS 1.3 / HTTP-3 handshake. Because QUIC is UDP, a TCP PEP can't intercept it — so QUIC RTT reveals the true path latency the proxy hides. It's also increasingly the protocol passengers actually use: YouTube, Google, and Cloudflare all default to HTTP/3.

The iPhone and Android apps run QUIC on its own timer at the Active cadence shown here (the apps have no separate Passive/background profile). On a degraded satellite link some UDP:443 handshakes genuinely time out, so QUIC keeps a minimum attempt floor (about one sweep every ~15s) rather than backing off — coverage thins but never stops.

HTTP HEAD

Full-stack latency — DNS + TCP + TLS + request/response — which is what you actually feel loading a page. Canary targets and response-header inspection also expose intercepting proxies along the path.

DNS resolve

End-to-end resolution time as applications experience it, via the system resolver — so it includes real OS DNS-cache effects rather than a synthetic lookup.

Traceroute

The full network path — every router hop with per-hop latency. Path changes correlate with performance changes; on satellite a path change can flag a beam handover or gateway switch, and the hop count itself is informative (satellite links show few hops).

Wi-Fi signal

RF and PHY-layer metrics from the adapter — RSSI, noise floor, SNR, channel, width, band, PHY mode, TX rate. Correlating signal with performance separates RF problems from network problems: a latency spike with an RSSI drop is local; one without points upstream.

Observed bytes

Passively reads the network interface's byte counters to track how much real traffic is flowing over the link. It sends nothing itself — it's context for how loaded the link was when each active probe ran.

Advanced — round composition & drop-from-end trimming

Round composition & cost

Each round's duration is the sum of its probe durations for the selected link. The heaviest round is tagged on GEO (the binding link).

RoundProbesDuration

Rounds that fit, by capture length

Drop-from-end trimming: a capture keeps as many leading rounds as fit its budget (after the one-time latency preflight), then stops.

CaptureGEOTerrestrial / LEO