How to Shape and Prioritize Network Traffic with Tc and Htb Queuing on Ubuntu Server
Learning how to shape and prioritize network traffic with tc and HTB queuing on Ubuntu Server gives you precise control over how bandwidth is distributed across your system. Without traffic shaping, a single process or user can consume all available bandwidth. This causes slowdowns for everything else running on the server. Traffic shaping lets you define rules that guarantee minimum speeds, cap maximum usage, and prioritize critical services. In this tutorial, you will set up the tc (traffic control) command with the HTB (Hierarchical Token Bucket) queuing discipline. HTB is one of the most flexible and widely used methods for shaping outbound traffic on Linux. By the end, you will have a working traffic shaping setup that prioritizes specific services and limits bandwidth hogs on your Ubuntu Server.
Prerequisites for Traffic Shaping with Tc and HTB on Ubuntu
Before you begin, make sure your environment meets these requirements.
Required access and software:
- Ubuntu Server 20.04 or 22.04 (fresh or existing install)
- Root or sudo access to the server
- The
iproute2package (installed by default on Ubuntu) - A basic understanding of networking concepts like IP addresses and ports
- Familiarity with the Linux terminal
Estimated time: 30–45 minutes.
You should know which network interface you want to shape traffic on. Run ip link show to list your interfaces. The most common names are eth0, ens3, or enp0s3. Replace eth0 in all commands below with your actual interface name. You can read more about the tc tool in the official tc man page. No additional packages are required beyond what Ubuntu installs by default.
How to Shape and Prioritize Network Traffic with Tc and HTB Queuing
See also: How to Containerize a Node.js Web Application with Docker
Follow these steps carefully. Each step builds on the previous one.
Step 1: Verify your network interface name
Run this command to confirm your active interface:
ip link show
Look for an interface that shows state UP. Note the name. This tutorial uses eth0 as the example.
Step 2: Remove any existing tc rules
Start with a clean slate. This command removes any existing queuing disciplines on the interface:
sudo tc qdisc del dev eth0 root 2>/dev/null
The 2>/dev/null part suppresses errors if no rules exist yet. Always run this before adding new rules.
Step 3: Add the root HTB queuing discipline
This command attaches HTB as the root queuing discipline:
sudo tc qdisc add dev eth0 root handle 1: htb default 30
The handle 1: labels this as your root class. The default 30 means any traffic that doesn’t match a specific class gets sent to class 30. You’ll define that class shortly.
Step 4: Create the root class with total bandwidth
Set your total available bandwidth. This example assumes a 100 Mbit connection:
sudo tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit burst 15k
This parent class acts as the ceiling for all child classes. No child class can exceed this total.
Step 5: Create child classes for traffic priorities
Now create three child classes: high priority, medium priority, and default (low priority).
sudo tc class add dev eth0 parent 1:1 classid 1:10 htb rate 50mbit burst 15k
sudo tc class add dev eth0 parent 1:1 classid 1:20 htb rate 30mbit burst 15k
sudo tc class add dev eth0 parent 1:1 classid 1:30 htb rate 20mbit burst 15k
Class 1:10 gets 50 Mbit for high-priority traffic like SSH or web services. Class 1:20 gets 30 Mbit for medium-priority traffic. Class 1:30 gets the remaining 20 Mbit for everything else.
Step 6: Attach SFQ to each class for fairness
SFQ (Stochastic Fairness Queuing) prevents any single flow from monopolizing a class:
sudo tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
sudo tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
sudo tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10
The perturb 10 option reshuffles the hash every 10 seconds. This improves fairness over time.
Step 7: Add filters to classify traffic
Filters tell tc which class to send specific traffic to. This example prioritizes SSH (port 22) and HTTP/HTTPS (ports 80 and 443):
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 22 0xffff flowid 1:10
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 80 0xffff flowid 1:10
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 443 0xffff flowid 1:10
Any traffic going to port 22, 80, or 443 gets routed to class 1:10 (high priority). All other traffic falls through to the default class 1:30.
Step 8: Verify your configuration
Check that everything applied correctly:
sudo tc qdisc show dev eth0
sudo tc class show dev eth0
sudo tc filter show dev eth0
You should see your HTB root, three child classes, SFQ qdiscs, and your filters listed. If anything looks wrong, go back to Step 2 and start fresh.
Step 9: Make the rules persistent
Traffic shaping rules don’t survive a reboot by default. Save them to a script and run it at startup.
Create a script:
sudo nano /usr/local/bin/tc-setup.sh
Paste all your tc commands into the file. Save and exit. Then make it executable:
sudo chmod +x /usr/local/bin/tc-setup.sh
Create a systemd service to run it on boot:
sudo nano /etc/systemd/system/tc-setup.service
Add this
[Unit]
Description=Traffic Control Setup
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/tc-setup.sh
[Install]
WantedBy=multi-user.target
Enable the service:
sudo systemctl enable tc-setup.service
Your rules will now apply automatically after every reboot. For deeper reading on HTB, check the Linux Traffic Control HOWTO maintained by The Linux Documentation Project.
Troubleshooting Common Tc and HTB Issues on Ubuntu
Error: “RTNETLINK answers: No such file or directory”
This usually means you used the wrong interface name. Double-check with ip link show and update your commands.
Rules don’t seem to work after reboot
Make sure your systemd service is enabled. Run sudo systemctl status tc-setup.service to check its status. Look for errors in the output.
Traffic isn’t being classified correctly
Run sudo tc -s filter show dev eth0 to see filter statistics. The packet counters tell you which filters are matching traffic. If a filter shows zero matches, check your port numbers and protocol flags.
All traffic goes to the default class
This is normal if your filters don’t match any traffic. Add filters for the specific ports or IP ranges your services use. You can also match by source IP using match ip src instead of dport.
Warning: Shaping only affects outbound traffic
HTB controls egress (outbound) traffic only. Shaping inbound traffic requires a different approach using ingress qdiscs and redirect tricks. Keep this in mind when planning your setup.
Conclusion
You now know how to shape and prioritize network traffic with tc and HTB queuing on Ubuntu Server. You set up a root HTB discipline, created priority classes, attached SFQ for fairness, and added filters to classify traffic by port. You also made the configuration persistent using a systemd service. This setup gives you real control over how your server handles bandwidth under load. From here, you can expand your filters to match specific IP addresses, add more priority tiers, or experiment with other queuing disciplines like HFSC. Traffic shaping is a powerful skill that makes a real difference
