Repository navigation
Releases
librespeed-go v4.1.0
The stability test shows the p50 and p95 of latency and jitter.
Highlights
- The Average and Jitter cards on the stability test show a
p50 · p95line under the low-high range, and so does each phase of the load table. The average hides how often spikes happen: p95 is the value 95% of pings stay at or below, so a few rare spikes leave it alone while frequent ones raise it. Jitter uses the change between two pings, the same values as the jitter average. The line shows once there are 20 values, and p95 is always a value that was measured. - Each small line under a value has a tooltip that says what it counts, over the whole test on the cards and over one phase in the table.
Changes
- Low-high ranges on the speed test, stability and statistics pages use a plain hyphen instead of a long dash.
- The note on the load table and the message after a test with no responses use plainer words.
- The README explains raising TCP buffers for fast users far from the server, links the article on speed test settings, and has a new stability screenshot.
Docker
docker pull ghcr.io/sprytnyk/speedtest-go:v4.1.0
Verify
gpg --verify SHA256SUMS.asc SHA256SUMS && sha256sum --ignore-missing -c SHA256SUMS
Full Changelog: v4.0.0...v4.1.0
librespeed-go v4.0.0
The server only speaks HTTP/1.1, so the download speed and the latency under load come out right.
Breaking
- The
enable_http2setting is removed. The server always uses HTTP/1.1, also when it serves TLS itself. Over HTTP/2 the browser puts every download stream and the latency pings on one TCP connection. On a 100 Mbps line that connection reached 30 to 45 Mbps, and the pings showed 220 to 350 ms instead of 44 ms. Deleteenable_http2from yoursettings.toml. The server ignores it if you leave it in.
Changes
- The page always downloads with 4 streams. Before, it used 6 when a reverse proxy served it over HTTP/2 or HTTP/3, but those streams share one connection, so the extra 2 didn't help.
xhr_dlMultistreaminsettings.jsonstill sets the count. - The README has a new section, "Getting accurate results". It covers turning off HTTP/2 and HTTP/3 in Caddy, keeping the default grace times, and switching the server to BBR congestion control. With all three, a 100 Mbps line measured 100 to 104 Mbps down instead of 36 to 42.
Docker
docker pull ghcr.io/sprytnyk/speedtest-go:v4.0.0
Verify
gpg --verify SHA256SUMS.asc SHA256SUMS && sha256sum --ignore-missing -c SHA256SUMS
Full Changelog: v3.1.0...v4.0.0
librespeed-go v3.1.0
Jitter is now a true average with its range shown everywhere, and the latency under load no longer goes missing.
Highlights
- Jitter is the average change between consecutive pings, on the speed test and the stability test. The old formula followed the latest pings, so a 100 ms spike followed by steady pings soon read as a few milliseconds.
- Every jitter and latency average comes with its lowest and highest value: under the Idle latency and Idle jitter cards on the speed test, under the Average and Jitter cards and in each row of the stability test's phase table, and in brackets on the statistics page.
- Results store the jitter's lowest and highest change (
jitterLowandjitterHighin the latency data). Older results show without them. - The result image shows the jitter next to the latency on the download and upload tiles.
- The stability test shows the latency's range under the Average card, in place of the separate Min and Max cards.
Fixes
- The latency while downloading and uploading sometimes showed "--". Pings slower than 5 seconds counted as lost, the ping on its way when a transfer ended was thrown away, and the pinger only loaded once the transfer had started. Pings during a transfer now wait as long as they take, the last one gets 2 seconds to come back, and the pingers load when the test starts. When no ping comes back at all, the page says "Latency: no reply". The stability test keeps its 5-second limit, where a slow ping counts as a failed request.
- The idle ping test started its lowest ping and jitter from a wrong value when its first ping failed.
Docker
docker pull ghcr.io/sprytnyk/speedtest-go:v3.1.0
Verify
gpg --verify SHA256SUMS.asc SHA256SUMS && sha256sum --ignore-missing -c SHA256SUMS
Full Changelog: v3.0.0...v3.1.0
librespeed-go v3.0.0
Latency under load: the speed test and the stability test show how much downloading and uploading slow your connection down.
Breaking
- The ISP lookup API is no longer built into the binary. It comes from
ip_lookup_urlonly, and without the key there are no ISP or country lookups. Thesettings.tomlin the repository, and so the Docker image, still sets it. If your ownsettings.tomldoesn't have the key, addip_lookup_url="https://ip.sprytnyk.dev/api/ip/{ip}/"(or another API), or setSPEEDTEST_IP_LOOKUP_URL, to keep the lookups.
Highlights
- The speed test measures latency and jitter while the download and upload run, and shows them on the download and upload cards, like Ookla's loaded latency.
- Ping and jitter become idle latency and idle jitter. Idle latency is now the average of the pings, so it compares directly with the latency under load, instead of the lowest ping.
- The latency while idle, downloading and uploading is stored with each result. The statistics page shows it in a Latency card, and the result image adds it to the download and upload tiles. The database schema doesn't change: it goes into the existing extra data. The stored ping is still the lowest, as before.
- The stability test has a Load option: idle for a third of the test, then downloading, then uploading. A table shows each phase's speed, latency, jitter and range, the chart shades and names the phases, and the verdict says how much the load raised the latency. The CSV export gets a phase column.
- The stability test can ping Mozilla and Wikimedia, besides this server, Apple, Cloudflare and Google.
- The statistics page and the result image show times as
2026-10-24 17:50in every language. - The select dropdowns are drawn in the pages' style in browsers that support it.
- The summary after a speed test is shorter: the speeds, then the latency of each phase.
Fixes
- The stability chart showed only the last minute, so in longer tests the earlier phases scrolled out of view. It now shows the whole test.
- Pings to an external target in the stability test always went past the target's CDN to its origin, so they measured the target's backend rather than your connection. They're now answered by the nearest edge.
- Long Ukrainian headings and labels on the statistics page no longer overflow the list or get cut off.
- The privacy policy now gives a contact address for deletion requests instead of a placeholder.
- Built with Go 1.27.3.
Changes
- Over HTTP/1.1 the speed test downloads with four streams instead of six, so the latency pings don't wait for a free connection. Over HTTP/2 it keeps six, and
xhr_dlMultistreaminsettings.jsonstill overrides it.
Docker
docker pull ghcr.io/sprytnyk/speedtest-go:v3.0.0
Verify
gpg --verify SHA256SUMS.asc SHA256SUMS && sha256sum --ignore-missing -c SHA256SUMS
Full Changelog: v2.1.0...v3.0.0
librespeed-go v2.1.0
Delete stored tests from the statistics page.
Highlights
- The statistics page can delete tests: one at a time from a test's page, a selection from the list using the new checkboxes, or every stored test at once with "Delete all". Each delete asks for confirmation first, in a dialog styled like the rest of the page, and the list then says how many tests were deleted.
- Deletes are only accepted from a signed-in session, as a POST and from the same origin, so another site can't trigger one through a signed-in browser.
Fixes
- With BoltDB, the statistics page said "Could not load the tests" while no test was stored yet. It now shows the empty list.
Docker
docker pull ghcr.io/sprytnyk/speedtest-go:v2.1.0
Verify
gpg --verify SHA256SUMS.asc SHA256SUMS && sha256sum --ignore-missing -c SHA256SUMS
Full Changelog: v2.0.1...v2.1.0
librespeed-go v2.0.1
Trailing slashes on every endpoint, and a Go update for a crypto/x509 vulnerability.
Fixes
- Every endpoint, including the
/backend/and.phpones, answers both with and without a trailing slash. Both are served directly instead of redirecting, because clients such as speedtest-cli drop the slash when they join URLs and can't follow a redirect on an upload. - The statistics page and its links use
/stats/. - Built with Go 1.27.3, which fixes GO-2026-6809: crypto/x509 now distrusts compromised intermediate certificates.
Docker
docker pull ghcr.io/sprytnyk/speedtest-go:v2.0.1
Verify
gpg --verify SHA256SUMS.asc SHA256SUMS && sha256sum --ignore-missing -c SHA256SUMS
Full Changelog: v2.0.0...v2.0.1
librespeed-go v2.0.0
Removes test ID obfuscation, and moves release builds to CI.
Breaking
- Test ID obfuscation is removed. Test IDs are random ULIDs, so it never made them harder to guess, and it needed a salt file kept across restarts.
enable_id_obfuscationis now ignored with a warning, and obfuscated result links handed out before return 404.
Changes
- Release archives are built by GitHub Actions for every version tag, with a reproducible build script (
scripts/build-release.sh). - Docker images are published for version tags only, as the version and
latest, instead of on every commit.
Docker
docker pull ghcr.io/sprytnyk/speedtest-go:v2.0.0
Verify
gpg --verify SHA256SUMS.asc SHA256SUMS && sha256sum --ignore-missing -c SHA256SUMS
Full Changelog: v1.3.0...v2.0.0
librespeed-go v1.3.0
A new look for every page, a statistics page worth using, Ukrainian translation, and a round of bug and security fixes.
Added
- Redesigned speed test page: results, a live speed chart and the connection details on one screen, with a layout for phones.
- Redesigned stability test with a compact dashboard, an always visible status and CSV export from the chart.
- Redesigned statistics page (
/stats): a table of the latest tests, and a detail view per test with readable ISP lookup, extra data, browser languages and a test log timeline. - Redesigned shareable result image, rendered at 1200×640 so it stays sharp on high-density screens.
- Ukrainian translation of all pages and the result image, picked from the browser's language or a flag switch. The result image takes
&lang=uk. ip_lookup_urlsetting (SPEEDTEST_IP_LOOKUP_URL) to use another ISP lookup API, or""to disable lookups.-versionflag, and the version in the startup log.
Fixed
- Every setting can now be set with a
SPEEDTEST_*environment variable; before, only keys with a built-in default could. - A missing or broken
settings.tomlgiven with-cnow stops the server instead of being ignored. - The statistics page read the configuration before
-cwas applied, and accepted obfuscated test IDs only in some places. - Test IDs were generated from a time-seeded random source and could be guessed; they now use
crypto/rand. - Results recorded behind a proxy lost the client address.
- Private address detection matched part of 100.60.0.0/14 as CGNAT and missed some of 100.64.0.0/10, and missed IPv6 link-local addresses outside
fe80:. url_basevalues with regular expression characters broke asset serving.- MySQL: the latest tests were not sorted by time, because the timestamp column was quoted with double quotes.
- PostgreSQL: user names and passwords with special characters broke the connection URL.
- SQLite: concurrent test results failed with
SQLITE_BUSYinstead of waiting for each other. - The memory database listed the oldest tests first.
- Database read errors while listing tests were silently ignored.
- Requesting the image of an unknown test returned 500 instead of 404.
- The result image printed the user's IP address in place of the ISP.
Security
- Reading request headers is limited to 10 seconds, so slow clients can no longer hold connections open forever.
- The statistics password is compared in constant time, accepted only from the sign-in form (never from the URL), and no longer sent a second time on the redirect after signing in.
- An empty
statistics_passwordkeeps the statistics page locked instead of accepting an empty password. - Built with Go 1.27.2, which fixes 12 standard library vulnerabilities reachable from this code.
Performance
- Test results are indexed by ID in every database schema; SQLite databases get the index on startup.
Removed
- The classic design and the design switch.
index.htmlis the only speed test page;index-classic.html,index-modern.htmland the old stylesheets, fonts and images are gone. - UPX compression of release binaries. It broke binaries under
MemoryDenyWriteExecute=truein the bundled systemd unit. - The el7 RPM spec and the ARM registry workflow.
- The
windows/arm(32-bit) build, which Go no longer supports.
Upgrade notes
- Links to
index-classic.htmlorindex-modern.htmlneed to point to/instead. - If you serve your own copy of the web UI with
assets_path, replace it with the newweb/assets, and keep yoursettings.jsonandserver-list.json. - Existing PostgreSQL, MySQL and MSSQL databases don't get the new index automatically; the README has the statements to add it.
- Go 1.27 or later is needed to build from source.
Docker
docker pull ghcr.io/sprytnyk/speedtest-go:v1.3.0
Verifying downloads
gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS
Signed with key F66E1C2EAED3905AEEB3323B5CAFBA6459067347.
🔖 v1.2.1
🔖 v1.2.0
ISP lookup
- ISP and country now come from the ip.sprytnyk.dev (https://ip.sprytnyk.dev/openapi.json) API, with no tracking: only the client's IP is sent, and private IPs are never sent at all.
- Removed the ipinfo.io lookup, the MaxMind/GeoIP .mmdb fallback and the distance calculation.
- rawIspInfo now contains ip, city, country, iso_code and org.
UI
- Added the connection stability test (stability.html) from upstream LibreSpeed, linked from both the classic and modern designs.
- Fixed horizontal scrolling in the modern design.
- The modern design now falls back to the local server when there is no server-list.json.
Maintenance
- Upgraded dependencies to fix Dependabot vulnerability alerts (librespeed#84).
- The Dockerfile now uses Go 1.25 (librespeed#80).
Breaking changes
- The ipinfo_api_key, geoip_database_file, server_lat and server_lng settings are no longer used. They're ignored if present, so you can delete them from your config.
- Anything that reads the loc, hostname, region, postal or timezone fields from rawIspInfo will no longer find them.
- The country field now holds the full country name (United States) instead of the ISO code (US), which has moved to iso_code