Internet Speed Not as Advertised: A Week of Evidence
The first scheduled run of a speed test, on a machine where nobody has ever run one by hand, produces no measurement at all.
It exits with status 1. It prints a licence notice, and then a line that only appears once the test has already refused to happen: To accept the message please run speedtest interactively or use the following: followed by speedtest --accept-license. That flag appears nowhere in --help and nowhere in the manual page shipped inside the download — I checked both against version 1.2.0.84 on 4 October 2026. The binary mentions it only when it has decided not to measure anything.
Point that task at a CSV file for a week and you come back to an empty file and no idea why. Which is this whole exercise in one shot. The measuring is the easy part. The evidence is the part that quietly does not get collected, and nothing tells you until the week you need it.
The run that fails is the row you need most
Here is the shape of the trap, tested rather than assumed. On a deliberately broken run — version 1.2.0.84, a server ID that does not exist, the two output streams kept separate — the process exited with status 2, wrote Configuration - No servers defined (NoServersException) to standard error, and wrote nothing at all to standard output.
Not a partial row. Not a row of zeros. Nothing.
So a log built as speedtest --format=csv >> rows.csv contains only the runs that worked. Every run that failed because the line was unreachable is missing from the file, and missing reads as "no test was scheduled" rather than "there was no internet." Ookla's manual lists the fatal cases plainly: Configuration - Couldn't connect to server (Network is unreachable), Failed to resolve host name. Cancelling test suite., Server Selection - Failed to find a working test server. (NoServers). Those are the minutes a service complaint is actually about, and the obvious command deletes them.
The fix is a wrapper, which is six decisions rather than a program:
- Record the local timestamp before the test starts, not after.
- Run the test with both streams captured to separate per-run files.
- Record the exit code. Zero means a row exists; anything else means it does not, and the code is itself data — 1 was the unaccepted licence, 2 was the server failure.
- On failure, append your own row carrying the timestamp, the exit code, and the first line of standard error in a
statuscolumn. - On success, append the tool's row with your timestamp prefixed to it.
- Never retry inside the slot.
That last one is the discipline people lose first, because the instinct when a run fails is to run it again. Resist it. A file with a failure row at 20:00 and a normal row at 20:15 tells a true story about a line that dropped. A file with one good row at 20:15 does not tell that story at all, and you were the one who erased it.
The scheduler has a matching trap, and it is the setting that looks most obviously correct. Microsoft's reference for New-ScheduledTaskSettingsSet (read 4 October 2026) documents -RunOnlyIfNetworkAvailable, whose worked example explains that Task Scheduler "runs the task only when a network is available." For almost any other job that is sensible housekeeping. For this one it means the exact moments you are trying to document never generate a run, a row, or a trace. Leave it off. Keep -WakeToRun and -DontStopIfGoingOnBatteries on if the machine sleeps, because a sleeping laptop at 3 a.m. produces the same silence as a dead line and the file cannot tell them apart afterwards. If you use -StartWhenAvailable so missed runs fire late, label every row by the clock time it actually ran rather than the slot you intended — a 20:00 run that executes at 23:40 is still a real measurement, just not a measurement of 8 p.m.
Ookla's CSV has no clock in it
Check this in your own file before you trust a week of it. Run with --format=csv --output-header and the header you get, verbatim, is:
"server name","server id","idle latency","idle jitter","packet loss","download","upload","download bytes","upload bytes","share url","download server count","download latency","download latency jitter","download latency low","download latency high","upload latency","upload latency jitter","upload latency low","upload latency high","idle latency low","idle latency high"
Twenty-one fields, and not one of them is a time. A CSV-only log is a stack of undated numbers whose only claim to being a week is the order of its lines, which is not a claim an argument can rest on. Prefix your own ISO-8601 local timestamp to every row. That is step one of the wrapper and it is not optional.
The machine-readable JSON does carry time, in UTC with a Z suffix, and it carries several things the CSV drops that answer objections before they are raised. A single run on 4 October 2026 returned "isVpn": false — a machine-recorded fact about the conditions rather than your assurance that the VPN was off. It returned "packetLoss": 0, an idle latency of 4.542 ms, and in the same run a loaded download latency whose high was 278.13 ms. It also returned a result object containing "persisted": true and a URL on Ookla's own site. That last part matters more than it looks: the result exists on a third party's server, dated, outside your control and outside your provider's. A spreadsheet can be edited after the fact. A set of result URLs cannot be, by you.
Two fields disappointed, and better to know now than in a reply letter. On the Windows build interface.name came back empty and macAddr came back as 00:00:00:00:00:02, so neither field tells anyone which adapter carried the test. If the method depends on the run being wired — and it does, for the reasons set out in the repeatable test method — then the wired part has to be enforced rather than promised. Bind the test to the Ethernet adapter — -I takes the interface, -i takes that adapter's IP address, and the manual describes both only as an "attempt to bind" — so a scheduled run is far less likely to go out over Wi-Fi while you sleep. Disabling the Wi-Fi radio for the week closes the gap the binding leaves, and keep a column for the adapter's negotiated link rate.
Keep the per-run raw JSON as a file named for its timestamp, and keep one appended table alongside it. The table is what people read. The JSON is what makes the table checkable by someone who doubts it.
The baseline is a number the company printed, with a date on it
Throughput figures prove nothing on their own. They become an argument only beside a published number, and which number you pick decides what sort of claim you are making.
Not the advertisement. "Speeds up to" is a ceiling, and nobody has to meet a ceiling. What you pin is the typical download speed, typical upload speed and typical latency printed on your plan's broadband consumer label, together with the plan identifier on it and the date you fetched it. Reading the label covers which lines mean what, and why the spreadsheet copy and the PDF are sometimes not the same document.
Why that number and not another: the duty it lives under is accuracy of disclosure. Paragraph (a) of 47 CFR 8.1 requires a provider to "publicly disclose accurate information regarding the network management practices, performance characteristics, and commercial terms" of the service. I pulled the section from the eCFR versioner API on 4 October 2026, against the 1 October 2026 issue of title 47, and two things in it are worth a household's attention today. Paragraphs (a)(3) and (a)(5) are both still live, so the machine-readable spreadsheet of all labels and the two-year label archive remain obligations, and (a)(5) still says a provider must "provide an archived label, upon request and within thirty days, to an existing customer whose service plan is associated with the particular label." The amendment published at 91 FR 52260 on 13 August 2026 is linked from the top of the section and has landed in part — the definition of "passthrough fee" now sits at (b)(2) — while the removals aimed at (a)(3) and (a)(5) have not.
Which sets the order of work. Fetch the label and the spreadsheet now, with the date, and if your plan changed inside the last two years ask in writing for the archived label of the plan you were actually sold. That thirty-day clock is enforceable while the paragraph exists, and nothing will announce the morning it stops being.
Then write a baseline block at the top of the file, before any measurement happens: plan name and identifier, typical download, typical upload, typical latency, the URL, the date fetched. Everything after it is a comparison instead of a complaint.
Seven days, with the gaps named out loud
The federal program is the model here, and its most useful habit is not its cadence. It is its bookkeeping about absence.
The Thirteenth Measuring Broadband America report rests on 30 days of data drawn from a two-month window, and its first footnote lists them individually: "September 12-13, and 16-21, 2022 (inclusive) plus October 4-6, 11-13, 15-17 and 19-31, 2022 (inclusive)." The reason is in the same footnote — the set was chosen "to avoid dates when outages arising from hurricanes and other natural disasters resulted in lower number of reporting whiteboxes." A later footnote adds that the window was picked "to provide an error-free reporting set of dates and to capture a time period where the maximum number of whiteboxes were reporting data," and states outright that "a number of dates were excluded within this validation period."
The Commission dropped days, then printed which ones and why. That is the transferable part. A household file does not need 30 days, and seven consecutive ones including a weekend will show a weeknight pattern, but it needs the same candor, because an unexplained hole in a log gets read as selection. A short block at the top does it: measurement window 6 to 12 October; no runs at 02:00 on 9 October (router firmware update, provider notice attached); slot 3 on 11 October ran at 23:40 rather than 20:00 (machine asleep, scheduler caught up).
The cadence itself — how many runs, in which windows, and why the 7 p.m. to 11 p.m. block is the one the FCC reports against — is already laid out in the repeatable method, and there is no reason to invent a different one.
One more borrowing, this time a self-exclusion. The report notes that some panelists were left out because they "had whiteboxes that were legacy models not fully supporting the plan speeds, or did not have enough data on reported testing dates." If your plan is sold above 940 Mbps and you are testing through a gigabit Ethernet port, say so in the baseline block. The report itself normalizes gigabit tiers to 940 Mbps for exactly that reason, as the repeatable method explains, so a shortfall above that line belongs to your hardware and should be written down as such in the baseline block, not left for the provider to point out.
Keep two data sets, because the Commission keeps two
The temptation at the end of a week is to tidy. One run at 2 a.m. came back absurdly low, and you know the backup was running, or someone was gaming, or something. Deleting it feels like honesty.
Look at what the FCC does with the same problem. The panel sample "is validated... and the measurement results are carefully inspected to eliminate any statistical outliers," which produces the validated data set each report is built on. And then, in the same breath, the Commission publishes the other one: raw data, described as raw precisely "because validation cross-checks are not done except in the test period used for the report."
Two files, one filtered, both available. Do that.
- The raw log. Append-only. Every run, every failure row, every timestamp. Nothing is ever removed from it and nothing is edited in it.
- The derived table. The same rows with your conversions applied and your filter rule applied, with the rule written out in one sentence at the top: rows marked
excludedwere excluded for the reason given in the notes column; no row was deleted.
Units are the other reason to separate the two. The machine-readable formats report speeds in bytes per second at maximum precision, so a figure like 101,200,169 becomes 809.6 Mbps only after dividing by 125,000. Keep the raw value in one column and the converted value in another. A file in which one number has been silently transformed is a file in which every number has to be re-checked by whoever reads it.
About 39 gigabytes, and who pays for it
This is the practical cost nobody mentions, and it is measurable rather than theoretical. A single pass of 1.2.0.84 on a gigabit-class line reported 707,804,364 bytes down and 678,426,954 up. That is 1.39 GB for one run, because the test moves as much data as the line will take for as long as it runs.
Four runs a day for seven days comes to roughly 39 GB. Against an unmetered fiber or cable plan that is nothing. Against a 100 GB satellite or fixed wireless allowance it is a serious fraction of the month, and spending the allowance to prove a speed problem — then being throttled for exceeding it — would be a self-inflicted wound with your own schedule as the cause. Check the allowance first. If it is tight, cut to two runs a day and extend to ten days, or use a single-connection ten-second test for the off-peak slots and save the heavier tool for the peak window.
Worth reading the licence while you are in there. Ookla's manual page states you may use the software and the information it generates "for personal, non-commercial use, through a command line interface on a personal computer." Documenting your own household line is exactly that. The page where Ookla advertises the CLI invites you to "set up automated scripts to collect connection performance data, including trends over time" within that boundary, and that invitation is the only permission this method needs.
The provider's records sit on a clock you do not control
Your log describes one end of the path. The other end has records too, and they are not kept for your benefit.
Ask for them in writing, early — in the first week of measuring rather than the week of filing. Every trouble ticket on the account with dates and dispositions. Every outage or maintenance notice affecting your service address inside the window. Any signal or error history the provider holds for your gateway. Use a written channel that produces a reference number, because a phone call starts no clock, a point the billing dispute ladder makes at length about deadlines that live in your own agreement rather than in any regulation.
Then collect the things on your own side that expire fastest. A modem or gateway status page typically holds its uptime, error counters and recent event log only until the next reboot, so screenshot it inside the window and again at the end, with the clock visible, instead of after an argument has started. If the provider posts a network status page for your area, save it on the days your log shows trouble. A provider notice confirming maintenance on the night your file has a gap is the difference between a hole and an explained hole.
One caution about citations in your own file, which applies to mine as much as yours: do not point a reader at a page you have not opened yourself that week. Footnote 22 of the Thirteenth Report cites an FCC page for the data collection policy behind its date exclusions. Requesting that address on 4 October 2026 through a text proxy returned the Commission's own "Page Not Found" template, and direct requests from this machine were refused by fcc.gov outright, so I cannot tell whether the page has moved or is merely closed to me. Either way it is not something I would cite as read.
Two thousand characters, and the file behind them
The last step is where most of this effort gets wasted, because people send everything and say nothing.
Start inside the provider. A written ticket naming the label figure, the measurement window, the count of runs below that figure, and the remedy you want, with the file offered as an attachment and a ticket number recorded. That step is what gives any later filing its date of first contact.
If it goes nowhere, the federal form is smaller than people expect. Reading the FCC's Internet Complaint form on 4 October 2026, the subject field counts down from 70 characters and the description from 2,000 — about 300 words for the entire case. The page sets expectations in its own words: the process "is designed to facilitate a conversation between you and your provider," and "the FCC cannot act as your personal lawyer, a court of law, or your legal advisor." It also notes that if you want to share information without it being passed to the company, there is a separate Your Story form for that. The issue-type list and any attachment control are drawn by scripts the proxy did not run, so I cannot say from the help pages whether the form takes files directly.
What happens after is mechanical, and the filing FAQ (read 4 October 2026) describes it: you get a tracking number and periodic status emails, the provider "is required to respond in writing to the complaint within 30 days of receipt," and it "must provide you and the FCC with a copy of the response." Two further sentences there are the ones to plan around. You can "amend or supplement your complaint by replying directly to the email that you received from the FCC," which is how a week of logs and per-run JSON becomes attachable without pasting any of it into a 2,000-character box. (The 39 GB is what the tests move over the line; the files themselves are small.) And if the response is inadequate, sending rebuttal information can trigger "a new obligation to respond."
So the bundle has three layers in a fixed order, sized to those constraints:
| Layer | What it is | Who reads it |
|---|---|---|
| The sentence | One claim: across N peak-window runs between two dates, X of them came in below the typical download speed of Y Mbps printed on the label dated D | Everyone, first |
| The summary | One page: baseline block, window, gap disclosures, counts, and the remedy asked for in dollars or in a service action | The person working the queue |
| The exhibits | Derived table as PDF, raw log as CSV, per-run JSON in a folder, label screenshot and spreadsheet, ticket correspondence | Only on request |
Name the files so the order survives being dropped into somebody else's folder: 01-summary.pdf, 02-derived-log.pdf, 03-raw-log.csv, 04-label-2026-10-04.pdf, 05-tickets.pdf. Redact before sending, too — the raw JSON records your public address in externalIp, which nobody processing a service complaint needs.
Which door the filing goes through after the provider, and whether your state commission has any jurisdiction over broadband at all, is a separate decision with a separate set of answers; the escalation order sets that out state by state. The evidence does not change between doors. Only the address does.
Tonight's work is twenty minutes and almost none of it is measurement. Run the tool once by hand so the licence is accepted and the first scheduled run is not thrown away. Open your label, write the baseline block, save it with today's date in the filename. Create the task with the network-availability condition switched off, point it at a wrapper that records the exit code, and let it fire once while you watch. Then open the file and check that there is a timestamp in the first column. If there is not, you have found the problem a week early rather than a week late.
Frequently asked questions
How many days of speed tests do I need before I complain?
Seven consecutive days including a weekend is enough to show a pattern, and the number of days matters less than whether you can account for every hour you claim to have measured. The FCC's own Thirteenth Measuring Broadband America report used 30 specific days drawn from a two-month window and published exactly which dates it dropped and why. Copy that habit rather than the number: name your gaps in the file instead of leaving holes a reader has to interpret.
Does a provider have to meet the typical speed on my broadband label?
No. The label is a disclosure under the transparency rule at 47 CFR 8.1, not a service level, and paragraph (a)(6) says in terms that the label is not a safe harbor from that rule. What your log can establish is that a figure the company published does not describe the service it delivered. That is an accuracy question about a published document, which is a narrower claim than breach of contract and considerably easier to support with 28 rows.
Will the FCC let me attach my spreadsheet to a complaint?
The Internet Complaint form itself is small. Reading it on 4 October 2026, the subject field counts down from 70 characters and the description from 2,000, so the complaint has to stand alone in roughly 300 words. The FCC's filing FAQ says you can amend or supplement a complaint by replying directly to the acknowledgement email it sends you, which is the practical route for files. Write the 2,000 characters so they are complete without the attachment, then offer the attachment.
How much data does a week of automated speed tests use?
More than most people expect, and it scales with how fast the line is. One pass of Speedtest CLI 1.2.0.84 on a gigabit-class line reported 707,804,364 bytes downloaded and 678,426,954 uploaded, which is 1.39 GB for a single run. Four runs a day for seven days is about 39 GB. On an unmetered fiber or cable plan that is noise. On a capped fixed wireless or satellite plan it can be a third of the monthly allowance, so check the allowance before scheduling anything.