One noisy upload can flatten everyone else's bandwidth, and tc-tbf fixes that by capping the sustained rate. You will attach a Token Bucket Filter to an interface, cap its sustained egress rate, inspect the resulting queue and remove it again. The examples use tc from iproute2 6.1.0, package version 6.1.0-1ubuntu6.4, as installed on this machine. Allow about fifteen minutes, plus a maintenance window if the interface carries real traffic.
You need the iproute2 package and an interface name you can safely test. Adding a root qdisc changes live outbound traffic and normally requires elevated privileges. Do not paste the examples unchanged into a production host: replace IFACE and choose a rate that your service can tolerate.
This is an ordinary read-only check:
$ tc -V
tc utility, iproute2-6.1.0, libbpf 1.3.0
$ ip link show dev IFACE
Replace IFACE with the interface you intend to shape, such as eth0. The second command should print that interface's link information. If it reports that the device does not exist, stop here and find the correct name with ip link.
Checkpoint: record the existing root qdisc before changing anything:
$ tc qdisc show dev IFACE
Keep this output. It is your recovery reference. A host may already have a root qdisc installed by NetworkManager, systemd-networkd, a container runtime or another traffic-control tool.
rate is the sustained shaping rate. burst is the number of bytes that can be sent immediately when the bucket has tokens. A larger rate generally needs a larger burst. If the bucket is too small, packets can be dropped because more tokens arrive during a timer tick than fit in it.
latency is an alternative way to set the queue size: it asks TBF to calculate a byte limit for the maximum time a queued packet may wait. Use either latency or limit, not both. The ordinary TBF queue behaves like a byte FIFO unless you attach an inner qdisc later.
For a deliberately modest test, use a sustained rate of 500 kbit/s, a 5 kB bucket and a 70 ms queueing target. These values are the manpage's representative configuration, not a universal recommendation. A 10 Mbit/s link, for example, needs a much larger bucket than this example.
Before running this command, warn anyone using the interface: packets will now be held or dropped to enforce the rate. Run it with sudo or an equivalent capability:
$ sudo tc qdisc add dev IFACE root handle 10: tbf rate 500kbit burst 5kb latency 70ms
A successful tc qdisc add normally prints nothing. The root position makes TBF the interface's root qdisc, and handle 10: gives it a stable identifier for inspection and for an optional child qdisc.
Checkpoint: inspect what is actually installed:
$ tc -s qdisc show dev IFACE
qdisc tbf 10: root refcnt 2 rate 500Kbit burst 5Kb lat 70.0ms
...
The counters and reference count vary. Confirm that the qdisc is tbf, that it is root, and that the displayed rate and latency match your request. If the command says that a parent or root already exists, do not keep retrying with add; inspect the current qdisc and decide whether replacing it is authorised.
Use a known, reversible test transfer or your application's normal test traffic. Then run:
$ sudo tc -s qdisc show dev IFACE
Look at the packet and byte counters, plus the backlog and drop counters. A non-zero backlog means packets are waiting for tokens. Drops indicate that the configured queue filled or that the bucket cannot absorb the traffic pattern. A zero backlog does not prove that traffic was sent at exactly the configured rate, because the interface and its driver may have further queues.
TBF is a shaper, not a scheduler. It can deliberately wait even when packets are available. It also permits a burst while tokens are available, so a short measurement can exceed the nominal rate. Measure over a sufficiently long interval and account for the initial bucket allowance.
The basic configuration limits the long-term rate but does not prevent every burst. If millisecond-scale burst control matters, add peakrate and minburst:
$ sudo tc qdisc replace dev IFACE root handle 10: tbf rate 500kbit burst 5kb latency 70ms peakrate 1mbit minburst 1540
peakrate limits how quickly the main bucket is depleted. minburst, also called mtu, sets the second bucket size. For accurate peak-rate shaping it should match the interface MTU, but a larger value permits more burstiness. The kernel's timer resolution limits perfect shaping, so a very small peak bucket can also limit achievable throughput.
replace is a live change, not a harmless preview. Use it only after checking the current qdisc and agreeing that a brief traffic disruption or changed queueing behaviour is acceptable. Verify the result again with tc -s qdisc show dev IFACE.
Removing the TBF is a state-changing operation. Do it when the resulting traffic interruption is acceptable:
$ sudo tc qdisc del dev IFACE root
$ tc qdisc show dev IFACE
Deletion removes the root TBF and its queue. It does not reconstruct a qdisc that another service installed before your test. If the original output from step 1 showed a managed qdisc, restore that exact configuration through its owner rather than guessing a replacement. For a temporary lab interface, an explicit tc qdisc add command from the saved configuration may be sufficient; for a managed production interface, let the network manager reapply its profile.
If a service depends on the queue remaining present, do not use the delete command as an emergency shortcut. First identify who owns the interface configuration and prepare the service-level rollback.
rate, a suitable burst and either latency or limit.tc -s qdisc show dev IFACE showed the expected TBF and useful counters.peakrate as optional burst control, not as a second sustained-rate setting.