Traffic Shaping and Throttling
The QoS article explained why not all traffic deserves equal treatment during contention, and it introduced tc and HTB just far enough to show that DSCP marking and queue-based prioritization are two separate steps — but it deliberately stopped short of reading tc output in full, promising that detail for later. This article delivers on that promise: not just which traffic gets priority, but the actual mechanics of slowing traffic down on purpose, and the meaningful difference between doing that gently versus doing it by force.
Two different problems that both get called "throttling"
"Throttling" in casual conversation covers two genuinely different techniques, and conflating them is the single most common confusion in this area:
Shaping delays packets that exceed a target rate, holding them in a queue and releasing them later, smoothing a bursty stream into a steadier one over time. A shaped connection that briefly wants to send at 100 Mbps against a 20 Mbps shaping target doesn't lose that excess traffic — it gets queued and sent slightly later, arriving intact but delayed.
Policing makes an instant, non-negotiable decision at the moment a packet arrives: does this fit within the allowed rate right now, or not? A policed packet that doesn't fit isn't queued for later — it's dropped (or, less commonly, remarked to a lower-priority class) immediately, with no buffering at all.
The practical difference shows up directly in behavior under a burst. Shaping adds latency during a burst, since queued packets wait their turn; policing adds packet loss during a burst, with a sender that discovers the drop only when TCP's own retransmission or the application layer notices something didn't arrive. Neither is strictly "better" — a video call would rather have a shaper's added delay than a policer's dropped frames, while a network operator protecting shared infrastructure from being overrun by one customer's traffic often prefers a policer's hard, immediate cutoff specifically because it doesn't accumulate a growing backlog of queued packets that a persistent overload would otherwise pile up.
Note
Shaping delays; policing drops. If you remember one sentence from this article, that's the one — nearly every other design decision in this space follows from which of the two a given situation actually needs.
The token bucket, seen from the network layer this time
Rate limiting and API gateways already introduced the token bucket algorithm at the application layer: a bucket holds tokens, tokens refill at a steady rate, and each request consumes one — a request arriving to an empty bucket gets rejected. The exact same algorithm runs one layer down, at the packet level, inside Linux's own traffic control system, and it's worth recognizing as the same idea rather than a new one: instead of gating HTTP requests, it's gating bytes leaving a network interface.
Linux implements this as the TBF (Token Bucket Filter) queuing discipline:
rate 20mbit is the steady long-term rate this interface is allowed to send at — the token refill rate. burst 32kbit sets the bucket's size: how much traffic can go out in an instantaneous burst before the shaper starts holding packets back, because a token bucket that held zero spare tokens would shape even a network's normal, harmless microbursts, which is rarely what anyone actually wants. latency 400ms bounds how long a packet is allowed to sit queued waiting for tokens before TBF gives up and drops it — a token bucket alone shapes indefinitely patient traffic; this parameter is what keeps a persistent overload from producing unbounded queuing delay instead of eventually just dropping the oldest excess.
qdisc tbf 8001: root refcnt 2 rate 20Mbit burst 4Kb lat 400.0ms
Sent 84213096 bytes 61204 pkt (dropped 312, overlimits 0 requeues 0)
Sent and dropped here mean specifically what they say: 61,204 packets went out successfully, and 312 were dropped because they'd been queued past the 400ms latency bound without tokens becoming available — a small but real signal that this interface briefly wanted to exceed 20 Mbit/s for long enough that even the burst allowance and the latency window couldn't absorb it. overlimits 0 looks almost contradictory next to a nonzero drop count, until you recall that overlimits on a simple tbf qdisc counts a different thing (packets that would have needed to wait) than the final drop decision, which only fires once the latency bound is actually exceeded — the two numbers answer different questions, not the same one twice.
HTB: shaping different classes of traffic differently
TBF shapes an entire interface to one flat rate. Real deployments usually need something more granular — an office's uplink split between video conferencing, general web browsing, and bulk backups, each with a different guaranteed minimum and a different ceiling. This is exactly the scenario HTB (Hierarchy Token Bucket) — the same qdisc the QoS article already showed in a single-line tc qdisc show — is built for, and it's worth seeing configured rather than just observed.
sudo tc qdisc add dev eth0 root handle 1: htb default 30
sudo tc class add dev eth0 parent 1: classid 1:1 htb rate 50mbit
sudo tc class add dev eth0 parent 1:1 classid 1:10 htb rate 30mbit ceil 50mbit
sudo tc class add dev eth0 parent 1:1 classid 1:20 htb rate 15mbit ceil 50mbit
sudo tc class add dev eth0 parent 1:1 classid 1:30 htb rate 5mbit ceil 10mbit
This builds a small hierarchy: an overall 50 Mbit/s ceiling on the interface (1:1), with three child classes under it. Class 1:10 — video conferencing, say — gets a guaranteed 30 Mbit/s and can borrow up to the full 50 Mbit/s if nothing else is using it. Class 1:20 — general browsing — gets a smaller 15 Mbit/s guarantee with the same generous ceiling. Class 1:30 — bulk backups, the default class every otherwise-unclassified packet lands in — gets only a 5 Mbit/s guarantee and is capped at 10 Mbit/s even if the link is otherwise idle, deliberately preventing one large backup job from ever fully claiming the shared uplink.
rate is each class's guaranteed floor; ceil is how far it's allowed to borrow above that floor from whatever the other classes aren't currently using — HTB's defining feature over a plain TBF shaper, and the reason it's the qdisc of choice whenever different traffic types share one physical link but shouldn't be treated identically. A class actually using its full ceiling during a moment when every other class is also busy simply can't borrow further; ceil only grants access to genuinely spare capacity, not a second guarantee stacked on top of the first.
class htb 1:10 root prio 0 rate 30000Kbit ceil 50000Kbit burst 1600b cburst 1600b
Sent 293841022 bytes 210442 pkt (dropped 0, overlimits 0 requeues 0)
rate 28500Kbit 19832pps
The trailing rate 28500Kbit 19832pps line is the actual current throughput this class is pushing, distinct from the configured rate 30000Kbit figure — reading tc -s class show regularly during a suspected contention issue is how you'd confirm whether a specific class is genuinely being held back by its own ceiling or is simply not generating enough traffic to need it.
Practical scenario: a "fast" upload speed test that doesn't match what users actually experience
A hosting provider ships each customer VM with an advertised 100 Mbit/s uplink, but a customer reports uploads to their own backup service consistently topping out around 20 Mbit/s no matter what they try, despite a fresh speed test to a nearby test server showing the full 100 Mbit/s.
qdisc tbf 8001: root refcnt 2 rate 100Mbit burst 12Kb lat 50.0ms
qdisc tbf 8002: parent 8001: rate 20Mbit burst 8Kb lat 300.0ms
Sent 991234871 bytes 743211 pkt (dropped 41823, overlimits 41823 requeues 0)
Two separate tbf shapers are stacked on the same interface, and the inner one — 8002, a child of the outer 8001 — caps at 20 Mbit/s with a substantial dropped count. This is a common hosting-provider pattern: the outer shaper enforces the advertised, sold rate (100 Mbit/s), while an inner shaper applies a separate, lower limit specifically to traffic matching some other criteria — commonly a specific destination port range or protocol the provider treats differently, whether for its own bandwidth-cost reasons or a customer-tier distinction the advertised headline rate doesn't mention. The customer's generic speed test happened to hit the outer limit, because the test server wasn't on whatever criteria the inner shaper was actually matching; their real backup traffic hit the inner one every time.
The fix here isn't a tc configuration change — the customer doesn't control this interface at all. It's raising the specific, evidenced mismatch (100 Mbit/s advertised, a confirmed 20 Mbit/s shaper actually applied to their real traffic pattern) with the provider directly, armed with the tc -s qdisc show output as concrete evidence rather than a vague "it feels slow" report, since "it feels slow" is dismissible in a way that a specific qdisc hierarchy and a nonzero dropped counter under a named 20 Mbit/s cap is not.
Practice exercises
- A live video call and a large file upload share one 50 Mbit/s uplink with no shaping configured at all. Using the shaping-versus-policing distinction above, explain which of the two traffic types would suffer more from an unmanaged link, and whether shaping or policing would better protect the video call specifically.
- Given the HTB hierarchy in the example above, if class
1:20(browsing) is completely idle and class1:30(backups) wants to send as fast as possible, what is the maximum rate1:30can actually reach, and why? - A
tc -s qdisc showoutput showsoverlimits 41823butdropped 0on atbfqdisc. Using the definitions above, explain what this combination tells you about what's happening on that link that adropped 0reading alone would not.
Shaping and policing both assume there's exactly one path, and one administrator, deciding how a given packet gets treated. The next article steps back to a question this one deliberately left unaddressed: once a packet leaves a shaped, prioritized interface and starts crossing networks nobody involved in the shaping decision controls, how long does that trip actually take, and what determines whether more bandwidth would even help?
Sources
- man7.org, tc-tbf(8) – Token Bucket Filter
- man7.org, tc-htb(8) – Hierarchy Token Bucket
- man7.org, tc-police(8) – Generic policing