How to Test Internet Speed from the Linux Terminal

Abstract illustration of one host branching to several speed-test paths converging on a throughput gauge

speedtest-cli is the terminal answer every Linux admin reaches for when a link feels slow. Speedtest.net has started rejecting the tool’s plain-HTTP calls with an HTTP 403, and the flag that fixes it is worth knowing before the next outage.

Each command below shows a fresh install: a pip install into a venv for the Python tool and a tarball unpack of Ookla’s official binary, screenshots straight from those runs.

What Speedtest CLI actually measures

speedtest-cli is a Python command line interface for Speedtest.net. It picks the server with the lowest latency from the server list, then measures the time data takes to reach your machine and the time it takes to leave it. Those are the two numbers that matter, download and upload, and both belong in any honest answer, as the top reply on Ask Ubuntu puts it: internet speed test means down and up load.

Around that pair, the output usually carries context you would otherwise fetch separately: ping, jitter, the server, and a result URL.

FieldWhat it tells you
PingRound-trip latency to the chosen server in milliseconds
Download and uploadThe two ISP-facing numbers, quoted in Mbit/s
JitterHow much the latency wobbles between packets, which is why two runs at the same speed can behave differently on a video call
Result URLA shareable Speedtest.net page for the exact test

Jitter is the field people skip and then wonder why the call stuttered at full bandwidth.

What you need before you install

  • A Linux machine with internet access and Python 3 installed.
  • Working sudo when you install through a distribution repository.
  • Nothing else. speedtest-cli needs no daemon and no account.

The name speedtest-cli covers two different tools, and I installed both before writing a line. The Python one is sivel’s speedtest-cli, and the other is Ookla’s own binary, called simply speedtest, which is what Speedtest.net’s official install path gives you.

Both hit the same server network, but they are separate builds with separate commands, and a later section covers when the binary is the better tool for the job.

Installing speedtest-cli with pip

The cleanest route is pip inside a virtual environment, so the install stays out of your system packages. I checked PyPI before writing this: the current release is 2.1.3, and it has not changed since 2021.

python3 -m venv speedtest-venv
speedtest-venv/bin/pip install speedtest-cli
pip install speedtest-cli completing in a venv
pip resolves and installs speedtest-cli 2.1.3

The venv matters more than it looks. When pip installs without one, the entry point can land in ~/.local/bin and a bare speedtest-cli can fail with command not found until you fix the PATH. Keep the invocation prefaced with the venv path and the whole problem is gone.

Installing speedtest-cli from your distribution’s repository

Debian, Ubuntu, Fedora, and Arch all carry the package, so on a desktop this is a one-liner instead of a virtual environment. On Ubuntu:

sudo apt install speedtest-cli

On Fedora:

sudo dnf install speedtest-cli

The distro route gets you speedtest-cli 2.1.3 too, since that is the newest release that exists. The version is stable, which is exactly why the failures below are worth the reading.

Running the test and reading the output

Run it with no flags for the standard verbose output, or use the simple form when you only want the three numbers. From the venv:

speedtest-venv/bin/speedtest-cli --simple
speedtest-cli simple output showing ping, download and upload
speedtest-cli –simple output

I ran the simple form right after the venv install, and the fresh wheel picked a nearby server with no configuration. Each line stands for one measured quantity: ping is the round trip to the chosen server in milliseconds, and download and upload arrive in Mbit/s, decimal bits, exactly the unit an ISP quotes.

A bare speedtest-cli reprints the same numbers wrapped in the whole step log from configuration through server selection. The detail is handy when you want to see which server it picked and noise once you only want the numbers.

For the flag inventory, –help is shorter than any cheat sheet:

speedtest-venv/bin/speedtest-cli --help
speedtest-cli help output listing all options
speedtest-cli –help

I read the –help output for this section rather than repeating a cheat sheet, and a pair of options earns its place. –bytes switches the display from bits to bytes, worth it when you are estimating an actual transfer rather than reading an ISP number, and –server pins the test to one server id from –list when the automatic pick is congested.

Installing Ookla’s Speedtest CLI

When speedtest-cli keeps failing against your network or your Python is newer than what the project targets, the maintained path is Ookla’s own binary. Distribution runs through packagecloud, so on Debian or Ubuntu:

curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh | sudo bash
sudo apt install speedtest

On Fedora, RHEL, or CentOS the same page swaps the second command for dnf after the RPM script:

curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh | sudo bash
sudo dnf install speedtest

For any distribution, including ARM machines where the repository sometimes has no matching packages yet, the tarball works directly. The cURL step below is the plainest form of the download command, and my page on downloading a file with cURL on Linux covers the flags in depth:

curl -LO https://install.speedtest.net/app/cli/ookla-speedtest-1.2.0-linux-aarch64.tgz
tar xzf ookla-speedtest-1.2.0-linux-aarch64.tgz
./speedtest --version

The tarball unpack was the whole install on my end, one download and one extraction before the version line printed 1.2.0.84. Before the first measurement, though, the binary stops and prints its license and privacy terms, then tells you what it wants next:

speedtest first run showing the license notice
speedtest accepts terms through two flags before any measurement

Accept both terms in the same command and the test runs:

speedtest --accept-license --accept-gdpr

The acceptance is stored once under ~/.config/ookla, so later runs skip the notice. Output reshapes itself around what you asked for: human-readable by default, and json or one of the machine formats when the consumer is another program.

Getting output you can script

Automation changes the meaning of the same test. A cron job or a monitoring hook wants bytes and timestamps, not prose. Both tools have a machine format for that.

speedtest-venv/bin/speedtest-cli --json
speedtest --accept-license --accept-gdpr --format=json --progress=no
speedtest json output in the terminal
Ookla speedtest builds a json result with ping, download, upload and a result URL

I pulled the JSON form for a scripted run and read download.bandwidth straight out of it. Ookla’s JSON carries bandwidth in bytes per second, so multiply by 8 for the Mbit/s your ISP quotes.

For a logging pipeline, the commandinline project keeps a compact CSV recipe: pull download.bandwidth and upload.bandwidth into jq, append with the timestamp, and cron it. When you schedule one, use the full path from which speedtest, because cron’s PATH is shorter than your shell’s. The cron and shell-scripting guide on this site shows how to put that schedule in place.

When speedtest-cli fails with HTTP 403

The failure looks like this, straight from the forum threads and issue reports:

Retrieving speedtest.net configuration...
Cannot retrieve speedtest configuration
ERROR: HTTP Error 403: Forbidden

The cause is transport, not a bug in the tool. Speedtest.net closed plain HTTP against its API, and speedtest-cli 2.1.3 still asks over HTTP unless you tell it not to. I verified the archive date and the 2.1.3 wheel on PyPI before trusting this repair, and the –secure flag forces the HTTPS route:

speedtest-venv/bin/speedtest-cli --secure --simple

The flag not being the whole fix has its own reasons. The project was archived in 2024 and its README still targets Python 2.4 through 3.7, so the interpreter you point it at matters as much as the flag.

If –secure keeps failing, your network may be blocking the API host itself, and no flag repairs a blocked host. Ookla’s binary from the previous section is maintained against current server behavior, so that is the point where the switch is worth it.

A 403 on the Ookla binary does exist and has a different cause: speedtest refuses to run until the license is accepted, and some server operators see the refusal as an HTTP rejection when scripted. The fix there is –accept-license, not –secure.

Where this leaves your connection check

Run the test several times across a several-minute window before drawing a conclusion from one number, and test from a machine plugged in by ethernet rather than over wifi, since the wifi link has its own limits separate from what the ISP owes you. To see which connections a test opens, the netstat and ss command reference lists them live. The next thing worth trying is the scheduled version, since a rolling log catches degradation that a single run never shows:

speedtest --accept-license --accept-gdpr --format=json --progress=no | jq '{when: now, down_mbps: (.download.bandwidth*8/1e6), up_mbps: (.upload.bandwidth*8/1e6)}'

Is speedtest-cli the same thing as the speedtest command from Ookla?

No. One is a Python script by Matt Martz and the other is Ookla’s official binary. They test against the same server network, but the flags differ, and maintenance status differs too.

Why does speedtest-cli give HTTP Error 403: Forbidden?

The tool asks speedtest.net over HTTP by default, and the API now answers such requests with 403. Try –secure, which switches the request to HTTPS.

Can a test cover only the download or only the upload?

speedtest-cli has –no-download and –no-upload for exactly this. The Ookla binary runs both halves by default and does not expose per-direction skips in 1.2.0.

Why is my upload so much slower than my download?

Connections are asymmetrical by plan design. Check your plan against both numbers separately. The download figure alone says nothing about upload.

How often should I run a speed test?

Automate it. Once an hour through cron gives a stable baseline. Manual spot checks only catch the moment you happened to run them.