zev tune measures your internet line in full (latency, jitter and loss to the router and beyond, speed both ways, bufferbloat, DNS, path MTU, IPv6, Wi-Fi), then fixes what holds it back one change at a time, keeps only what measurably helped, and never leaves you without a connection.
A line can be "fast" and still feel bad: a video call freezes whenever someone uploads a photo, a game lags in spikes, pages hang for a second before they start. Speed tests rarely show why. The usual reasons are **jitter** (round trips that swing), **loss** (packets that never arrive), and **bufferbloat**: a full line queues packets in a modem or a phone for hundreds of milliseconds, and everything else waits behind them.
If something is broken rather than slow (no internet at all, names that don't resolve, a proxy that's gone), start with [zev doctor](/docs/en/doctor): it finds which layer is broken.
`zev tune check` measures all of it from this computer, says where the trouble is (your Wi-Fi, this computer, or beyond the router) and what it would fix. `sudo zev tune on` then fixes it **one change at a time**: after each change it checks at once that the internet still answers (else the change comes off), measures what that change is for against the line just before it, and keeps it only if it helped or cost nothing. At the end it measures the whole line again and shows before and after side by side. Linux, for now.
```bash
zev tune check # measure in full: no changes, no sudo
sudo zev tune on # measure → fix one at a time → measure again
zev tune status # what is changed, before and after, the live queue
sudo zev tune off # everything back exactly as it was
```
```diagram
type: flow
title: How sudo zev tune on changes your line
---
Measure | Round trips to the router and three places on the internet, idle and while the line is full both ways; speed; DNS; path MTU; IPv6; the Wi-Fi card | accent | 📏
~ what holds it back
Plan | Only the fixes the numbers call for, safest first, and only what this kernel and network card can do | | 🧭
~ one change
Connection check | Right after the change: does the internet still answer? If not, the change comes off at once | warn | 🛟
~ it answers
Measure that change | What the change is for (router jitter, delay under load, upload speed) against the line just before it | | 🔬
~ helped, or cost nothing
Keep | The next fix starts from the line with this one; one that didn't help is put back | good | ✅
~ all tried
Measure again | The whole line, the same way as before; if it is worse overall, everything comes off | accent | 📊
```
## Start in one minute
```diagram
type: cards
title: A better line in three commands
---
1️⃣ | Look | zev tune check: about 25 seconds; the line's numbers, what it's good for, where the trouble is, and what would be fixed
2️⃣ | Fix | sudo zev tune on: each fix tried, checked and measured; only what helped stays
3️⃣ | See | zev tune status: what changed and by how much; sudo zev tune off undoes all of it
```
Close what is downloading or streaming first: zev tune fills the line on purpose, and other traffic makes the numbers noisier. `--quick` measures in about half the time.
```bash
zev tune check # the full measurement, about 25 s; changes nothing
zev tune check --quick # about half the time
zev tune check --proxy # also through zev proxy share: what your proxy server adds
zev tune check --json | jq .score # the numbers for a script
sudo zev tune on # measure, fix one thing at a time, measure again
sudo zev tune on --only shaper,wifi_power # only these fixes (when the numbers call for them)
sudo zev tune on --skip bbr # everything but this one
sudo zev tune on --no-service --no-failover # until the next reboot, and never switching connections
zev tune status # what changed, before and after
sudo zev tune off # everything back as it was
```
## Every command
| Command | Example | What it does |
|---|---|---|
| `check` | `zev tune check` · `zev tune check --quick` | measures the line in full; changes nothing, needs no sudo |
| `check --proxy` | `zev tune check --proxy` | also measures through `zev proxy share`: what your proxy server adds |
| `check --json` | `zev tune check --json \| jq .score` | the numbers for scripts |
| `on` | `sudo zev tune on` | measure, fix one at a time (each checked and measured), measure again |
| `on --only` | `sudo zev tune on --only wifi_power,shaper` | only these fixes (when the numbers call for them) |
| `on --skip` | `sudo zev tune on --skip bbr` | everything but these |
| `on --no-service` | `sudo zev tune on --no-service` | the fixes last until the next reboot; no zev-tune service |
| `on --no-failover` | `sudo zev tune on --no-failover` | the service never switches to a second connection |
| `status` | `zev tune status` | what is changed, before and after, the smart queue's live rates |
| `networks` | `zev tune networks` · `zev tune networks --json` | every network measured, its score before and after, its queue's rates |
| `forget` | `sudo zev tune forget "wlan0 via 192.168.1.1"` · `sudo zev tune forget all` | measure that network again next time |
| `history` | `zev tune history` · `zev tune history --days 7` · `zev tune history --json` | the line hour by hour (or day by day): latency, added delay, unanswered, time down, switches |
| `links` | `zev tune links` · `zev tune links --json` | every connection to the internet, checked through its own device, and which one carries traffic |
| `off` | `sudo zev tune off` | everything put back exactly as it was, and the service removed |
## What it measures
| | How | Why it matters |
|---|---|---|
| **Latency** | round trips to Cloudflare, Google and Quad9, 20 a second; the steadiest one stands for "the internet" | every click waits at least one round trip |
| **Jitter** | how much consecutive round trips differ (and RFC 3550's smoothed jitter); the worse of the two | calls and games need round trips that don't swing |
| **Loss** | round trips that never came back | each lost packet costs a resend, and TCP slows down |
| **The router** | the same round trips to your router (the gateway) | if the router already swings, the trouble is your Wi-Fi or cable, not the provider |
| **Speed** | four streams at once, down and up, counted from the network card every quarter second after the first 1.5 s | what the line carries, and how much it swings |
| **Bufferbloat** | latency **while** downloading and **while** uploading, minus idle | how much a busy line delays everything else; graded A+ to F |
| **DNS** | how long the system's own lookups take, cached and fresh names | slow names make every new site slow |
| **Path MTU** | the biggest packet that gets there whole, and whether bigger ones vanish without a word | a "black hole" makes connections hang on big transfers |
| **IPv6** | whether this computer has IPv6, and whether it reaches the internet | broken IPv6 makes connections wait for it first |
| **TCP resends** | the system's resent segments during the upload | loss you can't see in pings |
| **Wi-Fi** | network name, band, signal, link rate, power saving | the card itself is often the cause |
Where ICMP (ping) is closed, round trips are timed with TCP handshakes instead; as root, a raw ICMP socket is used where ping sockets aren't allowed.
```diagram
type: graph
title: Where the delay comes from
alt: This computer reaches the router over Wi-Fi or a cable; the router reaches the provider and the internet. Round trips to the router show the Wi-Fi's share; round trips beyond show the whole way; while the line is full, the queue in the modem or phone shows as bufferbloat
legend: path=Measured idle; path2=Measured while the line is full
start: This computer
caption: Comparing the router's numbers with the internet's tells where the trouble is.
height: 46
---
node pc | This computer | 8 22 | host | accent | zev tune runs here: the card, its power saving, TCP, the queue going out
node gw | Router / phone | 38 22 | router | | Round trips here are the Wi-Fi's share alone
node isp | Provider | 64 22 | cloud | | Loss beyond the router is here
node web | Internet | 92 22 | cloud | | Cloudflare, Google, Quad9 stand for it
link pc gw | Wi-Fi or cable | path
link gw isp | the modem's queue | path2
link isp web | | path
note 38 40 | a full line queues here: bufferbloat | warn
```
## Reading a report
A real one, from a laptop on a phone's hotspot:
```text
zev tune check how good this internet line is, and what holds it back
wlo1 Wi-Fi "Phone" 2.4 GHz, signal -55 dBm (excellent), link 72 Mbit/s, power saving on
direct · zev dns off
idle downloading uploading
latency 90.6 ms 119 ms (+28) 181 ms (+90)
jitter 5.0 ms 12.1 ms 11.3 ms
loss 0.0% 0.0% 0.0%
router 3.2 ms ± 1.1 ms, loss 0.0% (downloading: 160 ms)
speed down 51.9 Mbit/s up 21.0 Mbit/s
bufferbloat A downloading, C uploading
DNS 28.6 ms
path MTU 1500
IPv6 none on this network
TCP resent 1.0% of what was sent
Score 70/100 browsing ~ streaming ✓ video calls ~ gaming ✗
• the router answers its own pings slowly while busy (+157 ms; phones do): traffic through it waits only +28 ms, so that is not a queue
• on 2.4 GHz with a strong signal: if the router has 5 GHz, it is usually faster and calmer
What sudo zev tune on would do (each one measured, kept only if it helps):
1. Wi-Fi power saving off — the Wi-Fi card sleeps between packets, which adds delay and jitter on every network
2. smart queue (CAKE), adjusted live — uploading adds a lot of delay to everything else (bufferbloat)
3. small TCP send queue — apps queue a lot of unsent data in the kernel, so small replies wait behind it
4. BBR congestion control — a long line: the default TCP takes long to get up to speed
```
- **latency (+N)**: the round trip while the line is full, and how much the full line added. Here an upload adds 90 ms to everything: a call stutters whenever a photo syncs.
- **bufferbloat** grades the added delay: A+ under 5 ms, A under 30, B under 60, C under 200, D under 400, F beyond.
- **router**: 3 ms and steady, so the Wi-Fi itself is fine idle. While downloading the phone answers its own pings in 160 ms, yet traffic through it waits only 28 ms more: a busy phone puts pings to itself last, which slows nothing else. A real queue on this side would slow both, and then the • line says so.
- **Score** puts latency, jitter, loss, bufferbloat, speed and DNS in one number; the marks say what the line is good for right now (✓ good, ~ works, ✗ poor).
- The **•** lines are what the numbers say in words, including what no setting can fix (a weak signal, a crowded band).
## What sudo zev tune on shows
After the same report as `check` ("Before"), each fix with what it did, then the line measured again. From the test lab's modem with deep buffers:
```text
Trying, one at a time:
1. smart queue (CAKE), adjusted live — a full line adds a lot of delay both ways (bufferbloat)
2. small TCP send queue — apps queue a lot of unsent data in the kernel, so small replies wait behind it
3. BBR congestion control — packets get lost here, and the default TCP slows down a lot for every loss
✓ smart queue (CAKE), adjusted live uploading +437 → +1 ms, 8.8 → 7.9 Mbit/s; downloading +272 → +0 ms, 29.4 → 25.9 Mbit/s
✓ small TCP send queue costs nothing; replies no longer wait behind queued uploads
✓ BBR congestion control upload 7.9 → 7.9 Mbit/s
After
before after
score 60/100 95/100 ▲ better
latency 20.2 ms 20.3 ms
jitter 0.1 ms 0.1 ms
loss 0.0% 0.0%
download 29.4 Mbit/s 26.0 Mbit/s −12%, the smart queue's share
upload 8.8 Mbit/s 7.9 Mbit/s −10%, the smart queue's share
bloat, down +272 ms D +0 ms A+ ▲ better
bloat, up +437 ms F +1 ms A+ ▲ better
```
`✓` kept, `↺` put back (it didn't help, or the connection stopped answering), `–` couldn't be set here. A fix is kept only when its own measurement says so; the last table is the whole line, measured the same way as before.
## The fixes
Each one is offered only when the numbers call for it, and only if this computer can do it.
| Fix | When | What changes | How it's checked |
|---|---|---|---|
| `wifi_power` | the Wi-Fi card has power saving on | `iw dev <card> set power_save off`: the card stops dozing between packets | round trips to the router again: jitter and median must not get worse |
| `shaper` | a full line adds 30 ms or more (bufferbloat B or worse) | **CAKE** on the way out at 90% of the measured upload, and on the way in (through an `ifb` device) at 90% of the download | the loaded test again: the added delay must fall by at least 30% and 10 ms, and speed may not drop more than 35% |
| `notsent_lowat` | uploading adds delay | `net.ipv4.tcp_notsent_lowat = 131072`: apps keep less unsent data in the kernel | costs nothing; checked with the smart queue |
| `bbr` | loss, many resends, a long line, or a swinging upload | `net.ipv4.tcp_congestion_control = bbr` | upload again: must not get slower or add more delay |
| `mtu_probing` | the path takes smaller packets, or bigger ones vanish | `net.ipv4.tcp_mtu_probing = 1`: TCP finds the size that gets through | costs nothing |
| `buffers` | the line holds more in flight than TCP's windows allow (a fast, long line) | `tcp_rmem`/`tcp_wmem` and `rmem_max`/`wmem_max` maximum raised to twice what the line holds | download again: must not get slower |
| `prefer_v4` | this computer has IPv6 but it doesn't reach the internet | one marked line in `/etc/gai.conf`: IPv4 addresses first | lookups and connections |
What zev tune never touches: someone else's queue setup on the interface (one with its own handle), an `/etc/gai.conf` that already has its own order, the interface's MTU, firewall rules, or anything on the router.
## Bufferbloat and the smart queue
A modem or phone that is full keeps packets in a deep buffer instead of dropping them. Your call's tiny packets wait behind a second of someone's upload. The fix is to make **this computer** the slowest point, just under the line's real speed: then the queue forms here, in CAKE, which keeps every flow's queue short and sends small packets first. The line loses about 10% of its top speed; everything else stops waiting. Traffic with your own network (the router, a NAS, a printer, IPv6 link-local) skips the queue both ways: only the internet is shaped.
Measured in the test lab (a modem with deep buffers, 30 Mbit/s down, 8 Mbit/s up):
```diagram
type: stats
title: Delay a full line adds, before and after zev tune
---
+438 ms | uploading, before | grade F: a call freezes whenever anything uploads | 100 | warn
+1 ms | uploading, after | grade A+, with CAKE at 90% of the upload (8.8 → 7.9 Mbit/s) | 1 | good
+276 ms | downloading, before | grade D | 63 | warn
+0 ms | downloading, after | grade A+, through the ifb device (29.4 → 25.9 Mbit/s) | 1 | good
```
### The queue follows the line
A fixed rate is wrong on Wi-Fi, a phone's hotspot or a busy evening: the line's speed changes. The **zev-tune service** (installed by `sudo zev tune on` where there is systemd) keeps measuring five round trips a second and moves the queue's rate:
- **more delay while that direction is busy** → the rate drops below what got through, so the far queue drains;
- **busy and calm** → the rate climbs 4% at a time, up to 1.6× what was measured;
- **quiet** → it settles back to the measured rate.
It **fails open**: on a network it hasn't measured, or when nothing answers for 10 seconds, the queue comes off; it goes back on when a measured network is back. It also puts the other fixes back after a reboot, and turns Wi-Fi power saving off again when a reconnect turns it on.
```diagram
type: sequence
title: The line gets slower; the queue follows
lanes: Line | zev-tune service | CAKE
alt: The line slows from 8 to 4 Mbit/s; round trips grow while uploading; the service lowers CAKE's rate below what got through; round trips fall back; when the line is idle the rate settles back
---
Line --> zev-tune service : round trips +40 ms while uploading
zev-tune service -> CAKE : 7.2 → 3.6 Mbit/s
note CAKE : the queue forms here again, short | good
Line --> zev-tune service : round trips back to +5 ms
== Quiet ==
zev-tune service -> CAKE : slowly back to the measured rate
```
## It remembers every network
Each network you run `sudo zev tune on` on is remembered with its own line: the smart queue's rates, what was kept, and the score before and after. Home, the office and a phone's hotspot each get their own. Back on one, the zev-tune service puts that network's queue back by itself, within five seconds of the router changing, without measuring again. On a network it has never measured, the queue steps aside.
```bash
zev tune networks
```
```text
Networks zev tune remembers back on one, its smart queue comes back by itself
network measured score queue down/up Mbit/s kept
wlan0 via 192.168.43.1 just now 44 → 86 - / 3.6 shaper, wifi_power
● wlan0 via 192.168.1.1 2 days ago 58 → 91 27.0 / 7.2 shaper, notsent_lowat
● = here. sudo zev tune forget <network> measures it again next time.
```
```diagram
type: sequence
title: Home, the hotspot, home again
lanes: You | zev-tune service | CAKE
alt: At home the service shapes at home's rates; on the hotspot it switches to the hotspot's remembered rates; on a café network it has never measured it takes the queue off; back home it puts home's queue back, all without measuring again
---
You -> zev-tune service : at home
zev-tune service -> CAKE : home's queue: 27 / 7.2 Mbit/s
You -> zev-tune service : on the phone's hotspot
zev-tune service -> CAKE : the hotspot's queue: 3.6 Mbit/s up | good
You -> zev-tune service : a café, never measured
zev-tune service -> CAKE : queue off (its rate would be a guess) | warn
You -> zev-tune service : home again
zev-tune service -> CAKE : home's queue back, no measuring | good
```
## The line's history
The service writes one sample a minute: latency at rest, the delay traffic added, the worst moment, what went unanswered, time with no answer at all, and which network and connection it was on. Two weeks are kept. It tells you **when** the line is bad, which a speed test never does, and it's something you can show your provider.
```bash
zev tune history # the last 24 hours, hour by hour
zev tune history --days 7 # day by day
zev tune history --json # every sample, for a spreadsheet or a ticket
```
```text
The line, hour by hour latency at rest + delay added by traffic; worst; unanswered; time without any answer
10-29 18:00 ▃ 24 ms + 4 worst 61 ms 0.2% unanswered wlan0 via 192.168.1.1
10-29 19:00 ▆ 25 ms + 31 worst 212 ms 1.4% unanswered wlan0 via 192.168.1.1
10-29 20:00 █ 26 ms + 58 worst 480 ms 6.1% unanswered 95 s down 1 event wlan0 via 192.168.1.1
10-29 21:00 ▃ 24 ms + 5 worst 70 ms 0.1% unanswered wlan0 via 192.168.1.1
trend ····················▃▆█▃
events
66 min ago back on wlan0 (it answered for 30 s; usb0 rests)
67 min ago switched to usb0: wlan0 stopped answering
```
A `·` in the trend is an hour with no sample: the computer was off or asleep.
```diagram
type: cards
title: Reading a row
---
24 ms | At rest | the round trip when the line is quiet: distance and the provider |
+31 | Added by traffic | how much longer it got while the line was busy. Under 10 is good; the smart queue keeps it there | accent
worst | The worst moment | the longest round trip that hour: what a video call felt |
unanswered, down | Lost and gone | replies that never came, and seconds with no reply at all | warn
```
## Two connections: when one stops, the other takes over
With more than one connection to the internet (Wi-Fi and a cable, or Wi-Fi and a phone over USB), the zev-tune service checks **each one through its own device** every five seconds, so it knows the second one works before it needs it. When the main one stops reaching the internet three times in a row (15 seconds), traffic goes through the best other one that answers. Once the main one has answered for 30 seconds it switches back, so a link that flickers doesn't make it flip back and forth.
```bash
zev tune links
```
```text
Connections to the internet each checked through its own device
wlan0 via 192.168.1.1 metric 600 doesn't answer
▶ usb0 via 192.168.42.129 metric 100 answers 48 ms
⇄ usb0 carries traffic now: the main connection stopped answering. Back on the main one after it answers for 30 s.
```
```diagram
type: flow
title: Failover, and back
---
Watching | Every connection is checked through its own device, every 5 s | | 👀
~ the main one misses 3 times
Switch | Traffic goes through the best other connection that answers; the smart queue rests | warn | ⇄
~ the main one answers for 30 s
Back | The main connection again, its queue back on | good | ✓
```
It only uses connections this computer already has: one you plugged in or joined. The switch is one route of its own (marked `proto 177`), which it takes away when it switches back or stops, so your network manager's own settings are never touched. Every switch is in `zev tune history`. To keep it from switching, use `sudo zev tune on --no-failover`.
## Never without a connection
- **One change at a time**, and right after each one a connection check (TCP handshakes, then pings, to three places, three tries): if nothing answers, that change comes off at once and the next one is tried.
- **A guard process** starts with `sudo zev tune on`. If zev tune on stops halfway (killed, the terminal closed, the SSH session cut), the guard sees it stopped and puts everything back.
- **Measured, not assumed**: a fix that didn't help is put back, and if the whole line measures worse at the end, every change comes off.
- **Only its own**: every queue zev adds carries handle `7701:`; `off` removes only those, and sets each setting back only if it still has zev's value.
- **Fails open** live, as above.
```bash
sudo zev tune off # everything back; the zev-tune service removed
```
## With zev dns, zev proxy and zev vpn
- **zev dns**: the report shows whether `zev dns` is on and how long names take; when they are slow or fail, it points at `sudo zev dns on`.
- **zev proxy**: `zev tune check --proxy` measures round trips and a download through `zev proxy share` beside the direct line, so you can see what your server adds; a server that swings a lot gets a hint to `zev proxy use auto`.
- **zev vpn**: with the VPN on, `sudo zev tune` measures the line itself, around the tunnel (that is what the fixes change), and says so; without sudo the measurement goes through the tunnel.
## Questions
**Does it slow my internet?** The smart queue takes about 10% of the top speed, and stays only when it took the delay down a lot. Everything else costs nothing measurable, and is checked.
**What if I move to another network?** The settings (power saving, TCP) stay. On a network zev tune has measured, its own smart queue comes back by itself. On one it hasn't, the queue steps aside, because its rate was measured on another line: run `sudo zev tune on` there too, and it's remembered from then on.
**Why did it keep nothing?** Then nothing measured better here. A line can be limited by things no computer setting reaches: the signal, the provider, the distance to the server.
**The speed test server doesn't answer.** zev tune uses Cloudflare's speed test. `ZEV_TUNE_DOWN_URL` and `ZEV_TUNE_UP_URL` point it at another (a GET that returns many bytes, a POST that takes them).
**Where are its files?** `/var/lib/zev/tune` (`state.json`: what changed and the old values; `live.json`: the queue and the connections now; `networks/`: one file per network measured; `history.jsonl`: a sample a minute, two weeks kept), and `/etc/systemd/system/zev-tune.service`.
**Does it switch to my phone's data when I don't want it to?** Only when the main connection stops reaching the internet for 15 seconds, and only to a connection this computer already has a route for (one you plugged in or joined). It comes back as soon as the main one has answered for 30 seconds. To keep it from ever switching, run `sudo zev tune on --no-failover`.
**My line is good. Does the service do anything?** Yes. `sudo zev tune on` with nothing to fix still installs it, to write the history and to switch connections if the main one stops (`zev tune status` says "watches this line").