The diagnostics release: a degraded test traces its own route, a log keeps every change of external address or provider, an outage is announced after several failures in a row rather than on each one, a token starts a test without the password, and a badge puts the line's last reading in a README. Upgrading runs three automatic migrations — a hop column on the tests, a token table and a connection-change log — with nothing to redo by hand. Two things happen on their own: connection changes are recorded from the first run after the upgrade, and on an instance that has no password or lets strangers read, /api/badge answers a public SVG. Everything else is off until you turn it on.
✨ New
- Route traces — when a test fails, falls under its baseline or misses the optimal ping by the margin the screen paints amber, MySpeed traces the route to the test server and shows the hop table in the test details, lossy hops marked. Off by default; the switch is under Optimal values. It uses the operating system's own tool —
tracert,traceroute, ortracepath, which the Docker image ships becausetraceroutewants a raw socket the container's user does not have — runs after the row is written and the integrations are told, so nothing waits on it, and is capped per round so a bad round cannot eat a scheduled one. Viewers never see the table; the export carries it and the import reads it back. - Connection changes — every Ookla, LibreSpeed and Cloudflare test already recorded the address the provider saw and the network it named, and nothing kept it. Changes are now written to a log of their own under Settings → Connection changes, compared per target so two targets on two WAN links do not read each other's address as a change, with IPv4 and IPv6 followed separately and a provider's name compared only with what that provider said before. Every notifier gains a switch and a template —
%ip%,%previousIp%,%isp%,%previousIsp%,%connectionChanges%— and the webhook postsIP_CHANGED. The log is pruned by the retention setting, cleared with the history and by a factory reset, and hidden from viewers and demos. - Outage alerts — a single failed test already reaches every notifier that asked for failures, which on an hourly schedule is a message an hour for a line that is really down and a message about nothing for a provider that hiccupped. Every notifier now offers one switch to be told once when a target has failed a number of tests in a row — its own number, two when blank — and once more when the first test after that succeeds. Both are edges, so a notifier set to three hears nothing about a two-failure blip at either end. The streak is read from the stored rows rather than remembered, so a restart mid-outage does not announce it twice. The webhook posts
OUTAGE_STARTEDandCONNECTION_RESTORED; the templates may use%failuresInRow%,%downSince%,%downtimeMinutes%and%outageSummary%. - API tokens — a script, a router hook or Home Assistant can start a test without holding the admin password. Issue one under Settings → API tokens; it is shown once and only its digest is stored. A token opens exactly two routes —
POST /api/speedtests/runandGET /api/speedtests/status/live— and is refused everywhere else. A forward-auth proxy's own Bearer header still reaches the password gate, and a wrong token spends no password budget, so a stale token in a polling automation cannot lock you out. Tokens travel in a configuration export only beside the other secrets. - A status badge (#936) —
GET /api/badgeanswers a small SVG in the shields.io style for a README or a status page:↓ 250 ↑ 40 Mbps · 12 msfrom the headline line's newest test,downin red when it failed,no dataon an empty instance.?metric=download|upload|pingprints one reading and?label=replaces the label. It sits behind the same door as the social preview image — a greyprivatebadge when the instance is closed, since an<img>cannot follow a JSON refusal — and may be cached for five minutes.
🐛 Fixes
- iperf3 can fetch its binary again — the publisher of the static builds MySpeed downloads rebuilt and replaced every asset on the release tag it pins, so from 6 September a fresh instance choosing iperf3 refused its own download on every platform, which is the digest guard doing its job. The pins are refreshed, each one checked against the build attestation GitHub holds for it rather than copied from what the host was serving. An instance that already had the binary was never affected.
- The target-met count reaches the screen — 1.5.4 shipped the server counting it and the row never rendering, because the page passes the overview card its props one by one and this one was not on the list.
- A schedule change or a pause during a round is seen — both guards sat inside the schedule-offset branch, so with the offset off a schedule replaced, or a pause pressed, while the round's config reads were in flight was seen by nothing and the test ran anyway.
- A shutdown during an iperf3 latency probe is immediate — the probe runs before the CLI is spawned, so there was nothing to terminate: shutdown waited out the whole ten-second sample budget against a silent host and then wrote its refusal into a closed database.
- Gotify accepts a key pasted with its line ending —
fetchrefused the header before the request left and every message was lost to an activity line; ntfy and InfluxDB already sanitised theirs. - InfluxDB points carry the test's own moment rather than the clock at the send, which sits behind the round and the queue ahead of it.
- A figure the provider did not measure is unmeasured everywhere — jitter, packet loss and the two loaded latencies were cleaned on their way into the row but passed raw to the notifiers, so a provider answering
-1for a loss it never measured storedNULLand told every webhook "-1%". - Nodes — a percent-encoded node id in the path is matched again, instead of the child answering the app page where the API was asked; a bodyless request is relayed without a fabricated
{}body; a node name longer than the column is refused with a message rather than failing inside the insert as a 500; and the node's context menu no longer replaces the page with "Oops!" when a status poll lands on the same click. - A restore judges more of what it is given — recommendation rows are checked the way nodes and targets already were, and a test at a time of day that does not exist (
25:61:99) is skipped rather than slipping past the future-skew check as aNaN. A full export now hands on integration data as an object rather than the JSON string SQLite stored it as. - An export narrowed to one target says so in its filename — that export and one of every target over the same range downloaded under the same name.
- A context menu wider than the viewport stays on screen — it was pushed back by its whole width with no floor, so on a narrow or zoomed viewport it landed off the other side.
- Smaller edges — the export-secrets switch is one control rather than two claiming the same click, the English "Last run:" label reads properly before "Just now", and
docker-install.shstops writing the obsolete Composeversionkey that made Compose V2 warn on every invocation.
63 new interface strings ship translated in all 23 languages, written here rather than through Crowdin, so they are worth a translator's eye.
Downloads
| Platform | x86-64 | ARM64 |
|---|---|---|
| Windows | EXE · MSI · EXE (no AVX2) · MSI (no AVX2) | |
| Linux | Binary · No AVX2 | Binary |
| macOS | Binary | Binary |
Source Distribution
- ZIP Archive (requires Bun runtime)
Docker Images
i7gamer/myspeed:latesti7gamer/myspeed:1.6.0