Home / Alt manpages / tc-htb(8)

  • tc-htb(8)
  • Admin command
  • linux

Shape an Interface with Linux HTB Classes

You will finish with a small Hierarchy Token Bucket (HTB) tree on one interface: a root qdisc, two child classes, a default destination for unclassified traffic, and commands to inspect and remove it. HTB shapes outbound traffic, so it is useful when one link needs separate bandwidth budgets rather than one undifferentiated queue.

Allow about fifteen minutes. You need the tc command from iproute2 and an interface whose root qdisc you are allowed to change. The examples were checked with iproute2 6.1.0-1ubuntu6.4. Root privileges are required for the commands that change qdiscs and classes; inspection commands may work without them.

Warning

Changing a production interface can alter latency and throughput immediately, and deleting its root qdisc can remove an existing traffic-control policy. Test on a disposable interface or maintenance window first. Record the original output of tc qdisc show dev IFACE so that you know what must be restored.

1. Choose the interface and record its current policy

Set a real interface name, then inspect its root qdisc. This is read-only and does not need elevated privileges:

$ IFACE='eth0'
$ tc qdisc show dev "$IFACE"
qdisc ... root ...

The exact qdisc and statistics are host-specific. Do not continue until you know whether another administrator or service already owns the root qdisc. The examples below deliberately use handle 1: and class identifiers 1:10 and 1:20; change them if those identifiers collide with policy you are keeping.

Checkpoint

Save the output somewhere outside this command history if the interface is shared. If you cannot explain how to restore the current root qdisc, stop before step 2.

2. Add an HTB root qdisc with a default class

The root qdisc provides the hierarchy. Here, unclassified traffic is sent to minor class 20 because of default 20. The r2q 10 value is explicit, although 10 is also the documented default divisor:

$ sudo tc qdisc add dev "$IFACE" root handle 1: htb default 20 r2q 10

There is no useful bandwidth limit yet. A root HTB qdisc needs classes underneath it to define the rates that traffic can use. If this command reports that a qdisc already exists, do not blindly add another one. Recheck step 1 and decide whether the existing policy should be changed or restored first.

Verify the new root before adding classes:

$ tc -s qdisc show dev "$IFACE"
qdisc htb 1: root refcnt 2 r2q 10 default 20
 ...

Statistics vary, so match the meaningful fields rather than copying the counters. You should see an HTB qdisc with handle 1:, divisor 10 and default minor identifier 20.

3. Create two classes with different budgets

Add classes directly below the root. rate is the bandwidth guaranteed to the class and its children. ceil is the maximum it may borrow up to when its parent has spare capacity; if omitted, it defaults to rate, which prevents borrowing. The burst values allow the token bucket to send short bursts:

$ sudo tc class add dev "$IFACE" parent 1: classid 1:10 htb \
    rate 10mbit ceil 20mbit burst 15k cburst 15k
$ sudo tc class add dev "$IFACE" parent 1: classid 1:20 htb \
    rate 5mbit ceil 10mbit burst 15k cburst 15k

These are example budgets, not measurements of your link. Set them below the real outbound capacity and choose burst sizes for the rates and timing behaviour you need. The manpage says that a parent's burst and cburst should be at least as high as the largest corresponding child value. With these values the root has implicit behaviour, so if you build a deeper hierarchy, plan its parent burst values deliberately.

Checkpoint

Inspect the hierarchy and confirm that both classes are attached to the intended root:

$ tc -s class show dev "$IFACE"
class htb 1:10 root ... rate 10Mbit ceil 20Mbit
class htb 1:20 root ... rate 5Mbit ceil 10Mbit

The display includes implementation details and counters that differ by iproute2 version. The class identifiers and rate limits are the useful checks.

4. Understand where packets go

HTB starts classification at the root. Filters attached to a class can direct a packet to another class. If no filter supplies an instruction, the packet is enqueued at the node it reached. A leaf class then holds the packet in its child qdisc, which is normally the default packet FIFO.

In this example, traffic that has not been directed elsewhere ends up in 1:20, because the root's default is minor identifier 20. This is a safety net, not a traffic policy for application names or ports. Creating classes alone does not split traffic between them. A real deployment must add and test suitable filters, which depend on the classification scheme you choose.

The lower numeric prio value is tried first when HTB performs its round-robin process. The examples leave prio unset because the rate and borrowing relationship is the point being demonstrated. Add priorities only when you have a clear scheduling requirement, and verify the resulting counters under representative traffic.

5. Check the result without guessing from counters

Ask tc for the complete qdisc and class state:

$ tc -s qdisc show dev "$IFACE"
$ tc -s class show dev "$IFACE"

Look for the root HTB handle, the default value, both class identifiers, and the configured rates. Packet and byte counters only become informative after traffic has passed through the interface. A class with zero bytes is not necessarily broken; it may simply have no filter selecting it and may not be the default class.

HTB shapes outbound traffic. It does not make the physical link faster, and it does not automatically classify traffic by process, port or protocol. Keep those separate concerns visible when troubleshooting: first confirm the hierarchy exists, then confirm classification, then assess the observed rate.

6. Remove the test policy and recover

Warning

The following command removes the HTB root and its child classes. It is destructive to this traffic-control configuration and may briefly change queueing on the interface. Run it only when you intend to discard the test tree:

$ sudo tc qdisc del dev "$IFACE" root

On the loopback interface used for a disposable test, deleting the temporary root returned the normal noqueue root qdisc. On a production or managed interface, restoration is site-specific: reapply the exact qdisc and class configuration recorded before step 2, or let the owning network service recreate it. Verify the final state:

$ tc qdisc show dev "$IFACE"
qdisc ... root ...

If you need to abandon the workflow after step 2 or 3, the same root deletion removes the HTB tree. Do not use it as an undo command unless you have confirmed that HTB is the policy you added and not someone else's existing root qdisc.

Done means

  • You recorded the interface's original root qdisc before changing it.
  • An HTB root has the expected handle, default class and divisor.
  • The child classes have explicit guaranteed rates, ceilings and burst values.
  • You understand that classes alone do not classify traffic into separate paths.
  • You inspected qdisc and class state with tc -s.
  • You removed the disposable tree or documented the exact restoration procedure for the managed interface.