speedtest-go: the LibreSpeed backend, self-hosted with a single Go binary
Go backend for LibreSpeed
At a glance
- What is it?
- speedtest-go is the Go backend for LibreSpeed, a speed test that runs in the browser with XMLHttpRequest and Web Workers instead of Flash or WebSockets, and that can ship as one static binary with no external dependencies. The interesting parts are the configuration file, the choice of telemetry database, and the differences from the PHP implementation.
- Who is it for?
- Adopt speedtest-go if you want to run your own speed test, on your own infrastructure, with results stored in a database you control, and if you can accept a client that needs JavaScript and XHR. Do not adopt it if you need a native client, or if you need the exact measurement methodology of a commercial test, since the browser based approach measures what the browser can achieve rather than a dedicated transfer.
- Can I use it commercially?
- Yes, with conditions. LGPL-3.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 67 days ago.
- What is it written in?
- Mainly HTML, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem: a speed test that should not report to somebody else
A speed test is a measurement, and a measurement you run on a server you own is a measurement you can trust and keep. The commercial tests are convenient, but they are also a third party service that sees your IP address, your ISP, and your network, and they are not always reachable from the network you are trying to measure.
LibreSpeed is that idea in the open. The tagline in the project description is blunt: No Flash, No Java, No WebSocket, No Bullshit. The implementation is a very lightweight speed test written in JavaScript, using XMLHttpRequest and Web Workers, and all modern browsers are supported, including IE11, the latest Edge, Chrome, Firefox and Safari, plus mobile versions.
speedtest-go is the Go backend for that front end, and it is the piece that makes the deployment story simple. The server requirements are short: any Go supported platform, with Go 1.21 or newer, a database if you want to store results, no external dependencies for a single binary deployment, and a fast internet connection.
The audience is an operator, a hosting provider or a network team who wants a speed test on their own infrastructure, and also a developer who wants to understand how a browser based measurement is put together.
The licence is LGPL-3.0, the language is Go with an HTML front end, the default branch is master, and the last push was on 2026-08-17. The releases are v1.1.6 from 2026-04-30, v1.1.5 from 2022-07-08 and v1.1.4 from 2021-09-18, so the recent history has a long gap followed by a single recent release.
How the measurement works without WebSockets
The technical decision that defines the project is the transport. The speed test is implemented in JavaScript using XMLHttpRequest and Web Workers, which means the browser downloads test payloads over ordinary HTTP requests and measures how long they take, rather than opening a persistent socket and streaming over it.
The consequence is compatibility. A WebSocket based speed test needs a server that speaks WebSockets and a browser that does too, and older browsers and restrictive proxies are a problem. An XHR based one works over the same HTTP plumbing as the rest of the web, which is why the list of supported browsers includes IE11, and why the project can claim no Flash, no Java and no WebSocket at all.
The Web Workers part is what keeps the measurement honest. A single thread downloading data while the page renders is measuring the browser as much as the network, so the transfers are handed to workers and the main thread only assembles the result. The feature list also covers the things a speed test is expected to report: download, upload, ping and jitter, plus the IP address, the ISP and the distance from the server as optional extras, telemetry, results sharing via a PNG image and a JSON API, and multiple points of test for choosing the closest server.
Two details are worth noting for an operator. The project supports the Proxy Protocol, so it can sit behind an HAProxy that forwards the real client address, and it offers ID obfuscation for test result privacy as an optional feature, which matters if your results are visible in a public statistics page.
Installing a prebuilt binary, and the single directory layout
The simplest install is a prebuilt binary from the releases page. The documented steps are to download the appropriate binary file, unzip the archive, make changes to the configuration, run the binary, and optionally set up a systemd service file.
There is also an Ansible role for installing speedtest-go, published on Ansible Galaxy, with a separate repository holding the documentation for the role. For a fleet of servers, that is the route that saves the most time.
The deployment layout is worth understanding, because it is the one thing the binary needs and it is easy to get wrong. Three things have to end up in the same directory: the assets directory, the settings.toml file, and the compiled binary.
The assets directory holds the font files and the JavaScripts, and it can contain multiple HTML pages which are then reachable directly under the server root, for example /example-singleServer-full.html. A default page mapped to / is a matter of putting a file named index.html under the assets directory.
There is a fallback for the assets. The settings file has an assets_path entry that defaults to assets in the same directory, and if the path cannot be found, embedded default assets will be used. That fallback is what makes the prebuilt binary work on the first run before you have customised anything, and it is also a quiet way for a deployment to end up serving the defaults instead of your own page, so the assets_path is worth setting explicitly.
If you would rather compile, the source route needs Go 1.21 or newer, and the build is a single command, with the assets directory and the settings file copied alongside the result.
Building from source, and the container image
The source build is short. You clone the repository, change into the directory, and compile:
$ git clone https://github.com/librespeed/speedtest-go# Change current working directory to the repository
$ cd speedtest-go
# Compile
$ go build -ldflags "-w -s" -trimpath -o speedtest main.goThe flags are the ones you would expect for a distributable binary: stripping the symbol table and the DWARF data, trimming the build paths so the binary does not leak the build directory, and naming the output.
The Dockerfile is more interesting, because it is a two stage build that ends in a scratch image:
FROM scratch
WORKDIR /app
COPY --from=build_base /build/speedtest ./
COPY settings.toml ./
EXPOSE 8989
CMD ["./speedtest"]The build stage uses golang:1.25-alpine, installs the compiler dependencies, downloads the modules and builds with CGO_ENABLED=0, which is what makes a static binary possible. The runtime stage has no operating system under it at all, only the binary and the settings file, and it exposes the default backend port 8989.
The go.mod shows what the project depends on, and the list is readable in a way that tells you what the server actually does: the chi router with its CORS and render packages for the HTTP layer, the ULID library for test identifiers, maxminddb-golang for the offline GeoIP database, go-proxyproto for the Proxy Protocol support, gorilla sessions and securecookie for the statistics page login, freetype and the x/image package for the PNG result images, the haversine package for the distance calculation, viper for the configuration file, bbolt and modernc.org/sqlite for the embedded databases, and the three network database drivers for MySQL, PostgreSQL and SQL Server.
The configuration file, and what each group of keys decides
The settings.toml file is the whole configuration surface, and it is worth reading as a map of the server's decisions.
The network group is the first. bind_address is the bind address, with an empty string to bind to all interfaces, and listen_port is the backend listen port, defaulting to 8989. proxyprotocol_port is the proxy protocol port, with 0 to disable. enable_tls and enable_http2 default to false, and the comment notes that TLS is required for HTTP/2, with tls_cert_file and tls_key_file to supply the certificates when you do.
The location and identity group follows. server_lat and server_lng set the server location, and zeroes mean fetch from the API automatically, and ipinfo_api_key is the key for that, if applicable. The geoip_database_file setting points at a MaxMind .mmdb file used as an offline fallback for ISP detection, and leaving it empty disables the fallback.
The telemetry group is the one that decides your operations. database_type supports none, memory, bolt, sqlite, mysql, postgresql and mssql, and the comment is explicit that if none is specified, no telemetry or stats will be recorded and no result PNG will be generated. The embedded options, bolt and sqlite, are pointed at a file with database_file, and the documentation notes that SQLite supports WAL mode for better concurrent performance and works out of the box with no additional dependencies. For the server databases you create the database and import the matching .sql file under the database directory, for example:
# assume you have already created a database named `speedtest` under current user
$ psql speedtest < database/postgresql/telemetry_postgresql.sqlThe last group is about exposure. statistics_password is the password for logging into the statistics page, and change this to enable the stats page, and redact_ip_addresses redacts IP addresses in stored results.
What the Go implementation does differently from the PHP one
The documentation has a section on the differences and caveats between the Go and PHP implementations, and it is the part to read before migrating.
Test IDs are generated as ULID, a Universally Unique Lexicographically Sortable Identifier, unlike the PHP version's auto-increment integer IDs. The practical effect is that identifiers are sortable by time and are not guessable, which changes what you can do with a results table. ID obfuscation is available as an optional feature, and when enabled the ULIDs are obfuscated with a per-instance salt, so the identifiers in a public statistics page do not correspond to the identifiers in your database.
The front end also differs. The Go version ships with two built-in UI designs, classic gauges and modern CSS, switchable through the ?design=new URL parameter, and the modern design, index-modern.html, supports multi-server configuration through a server-list.json file placed alongside the binary.
Server location can be defined in the settings or auto-detected at startup, and that auto-detection has a cost worth knowing about. There might be a slight delay on program start if your internet connection is slow, because the program will attempt to fetch your current network's ISP info for the distance calculation between your network and the speed test client. This action is only taken once and cached for later use.
That last detail is the one to plan for in a container that starts on every deploy, since a slow ISP lookup becomes a slow container start.
Licence, maintenance and where LibreSpeed is the wrong answer
The licence is the GNU Lesser General Public License, version 3 or later, with the copyright held by Federico Dossena for the earlier work and Maddie Zhan for the later. The LGPL is the right choice for a web server that people self-host and modify, since it allows closed modifications without requiring the whole stack to be published.
The maintenance picture is thin in one respect and fine in another. The last push was on 2026-08-17, so the code is being worked on, and v1.1.6 was published on 2026-04-30. But the release before it is v1.1.5 from 2022-07-08, so the project went three years and a half without a tag, and the recent activity is a single release rather than a cadence. Anyone running this in production should treat the pinned version as the thing to test, and read the CHANGELOG rather than assume the tip of master is what you deployed.
Where LibreSpeed is the wrong answer is about what you are measuring. A browser based test measures what the browser and the network between them achieve together, with the client machine's CPU in the loop, so it is a good relative measure and a poor absolute one. If you need the absolute number, with a purpose built client on a quiet machine, this is not the tool.
It is also the wrong answer for a client without JavaScript, since the whole measurement runs in the browser. And if you need attribution, per user reporting and abuse handling, the optional telemetry and statistics page are basic: the login is a single shared password, and the identifiers are only obfuscated when you turn that on.
For a hosting provider or a network team that wants a neutral speed test on their own infrastructure, with results in a database they own, this is a good fit. The judgement rests on the deployment: one binary, a settings file, an assets directory, and a database only if you want the history.
Editorial conclusion
Adopt speedtest-go if you want to run your own speed test, on your own infrastructure, with results stored in a database you control, and if you can accept a client that needs JavaScript and XHR. Do not adopt it if you need a native client, or if you need the exact measurement methodology of a commercial test, since the browser based approach measures what the browser can achieve rather than a dedicated transfer. Before you deploy it, decide whether telemetry is on or off, because the database type in settings.toml changes what you have to operate, and put the statistics page behind the password setting if you enable it.
Frequently asked questions
What are the server requirements for speedtest-go?
Any Go supported platform, with Go 1.21 or newer, a SQLite, BoltDB, PostgreSQL, MySQL or MSSQL database to store test results if you want history, no external dependencies for a single binary deployment, and a fast internet connection.
How do I install speedtest-go from source?
Clone the repository, change into the directory and compile with go build -ldflags "-w -s" -trimpath -o speedtest main.go, then copy the assets directory, the settings.toml file and the compiled binary into a single directory so the server can find them.
Which telemetry databases does speedtest-go support?
The database_type setting supports none, memory, bolt, sqlite, mysql, postgresql and mssql. If none is specified, no telemetry or stats are recorded and no result PNG is generated, and the embedded bolt and sqlite options are pointed at a file through database_file.
How is the Go version different from the PHP LibreSpeed?
Test IDs are generated as ULID rather than auto-increment integers, ID obfuscation with a per-instance salt is available, the Go version ships two built-in UI designs switchable with ?design=new, and the modern design supports multi-server configuration through a server-list.json file. It also auto-detects the server location at startup, which can add a delay the first time it fetches ISP information.
What licence is speedtest-go released under?
The GNU Lesser General Public License version 3 or later, with the copyright held by Federico Dossena for the earlier work and Maddie Zhan for the later. The latest release is v1.1.6 from 2026-04-30 and the last push was on 2026-08-17.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/librespeed-speedtest-go)Was this useful?