A shaping problem that looks like the network is at fault is often just a qdisc nobody remembers setting, and tc is how you find out. This gives you a repeatable way to inspect traffic control, read queue statistics, save machine-readable output, and understand the identifiers you will need before touching a queueing discipline. The examples use tc from iproute2 6.1.0, installed here as package version 6.1.0-1ubuntu6.4.
Allow about fifteen minutes. You need a shell and an interface name. Inspection is normally unprivileged; changing a device's traffic control normally needs elevated privileges and can affect live traffic. This guide starts with read-only commands and does not ask you to replace a production qdisc as a first test.
Confirm which binary and package you are using. These commands only read local state:
$ command -v tc
/usr/sbin/tc
$ tc -V
tc utility, iproute2-6.1.0, libbpf 1.3.0
$ dpkg-query -W -f='${Package} ${Version}\n' iproute2
iproute2 6.1.0-1ubuntu6.4
The manual describes tc as the tool for configuring kernel Traffic Control. Shaping and scheduling affect outgoing traffic. Policing applies to incoming traffic, and dropping can apply in either direction. Keep that distinction in mind when an apparently correct command does not affect the traffic you are watching.
Set a real interface name from your host. Do not copy enp0s31f6 unless it exists on your machine:
$ DEV=enp0s31f6
$ tc qdisc show dev "$DEV"
qdisc fq_codel 0: dev enp0s31f6 root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms
Your qdisc and options will differ. A line such as qdisc noqueue ... root is still useful output. If the device name is wrong, tc reports an error and changes nothing. To see every device, omit the device filter:
$ tc qdisc show
qdisc noqueue 0: dev lo root refcnt 2
qdisc fq_codel 0: dev enp0s31f6 root refcnt 2
Checkpoint: identify the line for the interface whose packets you actually care about. The default root qdisc is not proof that traffic is shaped to a particular rate.
Use statistics when you need evidence that packets are passing through a qdisc:
$ tc -s qdisc show dev "$DEV"
qdisc fq_codel 0: dev enp0s31f6 root ...
Sent 785160025485 bytes 586285155 pkt (dropped 171, overlimits 0 requeues 14608282)
backlog 0b 0p requeues 14608282
Numbers are counters from the live host, so yours will not match. The useful fields are sent bytes and packets, drops, overlimits, requeues, and backlog. A zero backlog means there is no queue waiting at the instant of the check, not that the interface has never been busy.
For scripts, prefer JSON rather than parsing the human display:
$ tc -j qdisc show dev "$DEV"
[{"kind":"fq_codel","handle":"0:","dev":"enp0s31f6","root":true,"refcnt":2,"options":{"limit":10240}}]
The exact JSON object contains more fields and changes with the qdisc. Check the command's exit status and parse the fields you need. Do not treat the sample values as defaults for another host.
A qdisc may be classless, or it may contain classes. Filters classify packets within a classful qdisc; they are not independent global rules. Ask for each object explicitly:
$ tc class show dev "$DEV"
$ tc filter show dev "$DEV"
$ tc chain show dev "$DEV"
No output can be a correct result, especially for a classless qdisc or a device with no filters. If output exists, record its parent and identifiers before changing anything. The -s, -d, -j and -o format options also apply to the relevant show commands.
Traffic Control identifiers have a major and minor hexadecimal number separated by a colon. A qdisc handle normally uses the major part, such as 10:, while child classes use that major number with their own minor part, such as 10:20. The displayed 0: on a simple root qdisc does not give you a class tree to edit.
Do not invent a parent, class ID or filter priority from an example. Read the installed state and the manpage for the specific qdisc first. add creates an object, change modifies one in place without moving it, and replace removes and adds an object with the same ID, creating it if absent.
Warning: The following commands change live networking. A replacement can alter latency, throughput or packet handling for every process using the interface. Record the current output first, use a maintenance window for important links, and keep a console or out-of-band path available.
$ tc qdisc show dev "$DEV" > "/tmp/tc-$DEV-before.txt"
$ sudo tc qdisc replace dev "$DEV" root fq_codel
$ tc qdisc show dev "$DEV"
qdisc fq_codel 0: dev enp0s31f6 root ...
This example changes the root qdisc to fq_codel with that qdisc's installed defaults. It is not a bandwidth limit. To undo this particular change, restore the configuration appropriate to your host, or use the saved inspection as a record for the administrator who owns the interface. Do not blindly run tc qdisc del dev "$DEV" root: deletion also removes child objects and filters, and may leave the interface with a different default behaviour.