Set up hierarchical token bucket (HTB) traffic shaping with tc to control bandwidth allocation, prioritize network traffic, and implement quality of service policies for optimal network performance.
Prerequisites
- Root or sudo access
- Network interface with outbound connectivity
- Basic understanding of networking concepts
What this solves
Network traffic shaping controls bandwidth usage and prioritizes critical applications by managing data flow rates. This tutorial shows you how to implement hierarchical token bucket (HTB) queueing with tc (traffic control) to create bandwidth limits, prioritize different types of traffic, and ensure quality of service for mission-critical applications.
Step-by-step configuration
Install traffic control utilities
Install the iproute2 package which contains tc and related networking tools.
sudo apt update
sudo apt install -y iproute2 tc
Identify network interface
Find the network interface you want to apply traffic shaping to. This is typically your primary ethernet or wireless interface.
ip link show
ip addr show
Remove existing qdisc
Clear any existing queueing discipline on the interface before setting up HTB.
sudo tc qdisc del dev eth0 root 2>/dev/null || true
sudo tc qdisc show dev eth0
Create root HTB queueing discipline
Set up the root HTB qdisc with a total bandwidth limit. This example limits total bandwidth to 10 Mbps.
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 10mbit
Create traffic classes with different priorities
Define traffic classes for different priority levels. High priority gets 4 Mbps guaranteed, medium gets 3 Mbps, and low priority gets 3 Mbps.
# High priority class (guaranteed 4 Mbps, can burst to 6 Mbps)
sudo tc class add dev eth0 parent 1:1 classid 1:10 htb rate 4mbit ceil 6mbit prio 1
Medium priority class (guaranteed 3 Mbps, can burst to 8 Mbps)
sudo tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 8mbit prio 2
Low priority class (guaranteed 3 Mbps, can use all available)
sudo tc class add dev eth0 parent 1:1 classid 1:30 htb rate 3mbit ceil 10mbit prio 3
Add fair queueing to each class
Attach Stochastic Fair Queueing (SFQ) to each class to ensure fair bandwidth distribution among flows within each priority 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
Create traffic classification filters
Set up filters to classify traffic into different priority classes based on ports, protocols, or IP addresses.
# SSH traffic (port 22) - high priority
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 sport 22 0xffff flowid 1:10
HTTP/HTTPS traffic (ports 80, 443) - medium priority
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip dport 80 0xffff flowid 1:20
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip dport 443 0xffff flowid 1:20
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip sport 80 0xffff flowid 1:20
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip sport 443 0xffff flowid 1:20
Bulk traffic (FTP, BitTorrent) - low priority
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip dport 21 0xffff flowid 1:30
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip dport 6881 0xffff flowid 1:30
Add DSCP-based classification
Configure filters to honor DSCP (Differentiated Services Code Point) markings for enterprise QoS compliance.
# EF (Expedited Forwarding) - highest priority
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip tos 0xb8 0xfc flowid 1:10
AF31 (Assured Forwarding 3,1) - medium priority
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip tos 0x68 0xfc flowid 1:20
Default/Best Effort - low priority
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip tos 0x00 0xfc flowid 1:30
Create network-based rules
Set up filters to prioritize traffic from specific IP ranges or subnets.
# Prioritize traffic from management subnet (192.168.1.0/24)
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 192.168.1.0/24 flowid 1:10
Medium priority for internal network (10.0.0.0/8)
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip src 10.0.0.0/8 flowid 1:20
Configure ingress traffic shaping
Set up ingress filtering to control incoming traffic rates using the intermediate functional block (IFB) device.
# Load IFB module and create virtual interface
sudo modprobe ifb
sudo ip link add ifb0 type ifb
sudo ip link set dev ifb0 up
Redirect ingress traffic to IFB interface
sudo tc qdisc add dev eth0 handle ffff: ingress
sudo tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0
Apply HTB shaping to IFB interface
sudo tc qdisc add dev ifb0 root handle 1: htb default 30
sudo tc class add dev ifb0 parent 1: classid 1:1 htb rate 8mbit
sudo tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 3mbit ceil 5mbit prio 1
sudo tc class add dev ifb0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit prio 2
sudo tc class add dev ifb0 parent 1:1 classid 1:30 htb rate 2mbit ceil 8mbit prio 3
Create persistent configuration script
Save your traffic shaping configuration in a script for easy reapplication after reboots.
#!/bin/bash
Traffic Shaping Configuration Script
Interface to apply shaping to
INTERFACE=eth0
TOTAL_BW=10mbit
INGRESS_BW=8mbit
Clear existing qdiscs
tc qdisc del dev $INTERFACE root 2>/dev/null || true
tc qdisc del dev $INTERFACE ingress 2>/dev/null || true
tc qdisc del dev ifb0 root 2>/dev/null || true
Setup egress shaping
tc qdisc add dev $INTERFACE root handle 1: htb default 30
tc class add dev $INTERFACE parent 1: classid 1:1 htb rate $TOTAL_BW
Priority classes
tc class add dev $INTERFACE parent 1:1 classid 1:10 htb rate 4mbit ceil 6mbit prio 1
tc class add dev $INTERFACE parent 1:1 classid 1:20 htb rate 3mbit ceil 8mbit prio 2
tc class add dev $INTERFACE parent 1:1 classid 1:30 htb rate 3mbit ceil 10mbit prio 3
Fair queueing
tc qdisc add dev $INTERFACE parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev $INTERFACE parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev $INTERFACE parent 1:30 handle 30: sfq perturb 10
Traffic classification filters
tc filter add dev $INTERFACE protocol ip parent 1:0 prio 1 u32 match ip dport 22 0xffff flowid 1:10
tc filter add dev $INTERFACE protocol ip parent 1:0 prio 1 u32 match ip sport 22 0xffff flowid 1:10
tc filter add dev $INTERFACE protocol ip parent 1:0 prio 2 u32 match ip dport 80 0xffff flowid 1:20
tc filter add dev $INTERFACE protocol ip parent 1:0 prio 2 u32 match ip dport 443 0xffff flowid 1:20
Setup ingress shaping with IFB
modprobe ifb
ip link add ifb0 type ifb 2>/dev/null || true
ip link set dev ifb0 up
tc qdisc add dev $INTERFACE handle ffff: ingress
tc filter add dev $INTERFACE parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0
tc qdisc add dev ifb0 root handle 1: htb default 30
tc class add dev ifb0 parent 1: classid 1:1 htb rate $INGRESS_BW
tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 3mbit ceil 5mbit prio 1
tc class add dev ifb0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit prio 2
tc class add dev ifb0 parent 1:1 classid 1:30 htb rate 2mbit ceil 8mbit prio 3
echo "Traffic shaping applied successfully"
sudo chmod 755 /usr/local/bin/setup-traffic-shaping.sh
sudo /usr/local/bin/setup-traffic-shaping.sh
Configure automatic startup
Create a systemd service to automatically apply traffic shaping rules on boot.
[Unit]
Description=Network Traffic Shaping
After=network.target
Wants=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/setup-traffic-shaping.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable traffic-shaping.service
sudo systemctl start traffic-shaping.service
Monitor and troubleshoot traffic shaping
View current traffic shaping configuration
Use tc show commands to display the current queueing disciplines, classes, and filters.
# Show queueing disciplines
sudo tc qdisc show dev eth0
Show traffic classes
sudo tc class show dev eth0
Show filters
sudo tc filter show dev eth0
Show detailed statistics
sudo tc -s class show dev eth0
Monitor bandwidth usage in real-time
Create a monitoring script to track bandwidth usage across different traffic classes.
#!/bin/bash
INTERFACE=eth0
echo "Traffic Class Statistics for $INTERFACE"
echo "======================================="
while true; do
clear
echo "$(date)"
echo ""
echo "Class Statistics:"
tc -s class show dev $INTERFACE | grep -E "(class htb|Sent|rate)"
echo ""
echo "Filter Statistics:"
tc -s filter show dev $INTERFACE | head -20
echo ""
echo "Press Ctrl+C to exit"
sleep 5
done
sudo chmod 755 /usr/local/bin/monitor-traffic.sh
Test bandwidth limiting
Use iperf3 to test bandwidth limits and verify that traffic shaping is working correctly.
sudo apt install -y iperf3
# On server (destination):
iperf3 -s
On client (source) - test different ports:
Test SSH traffic (should get high priority)
iperf3 -c server_ip -p 22 -t 30
Test HTTP traffic (should get medium priority)
iperf3 -c server_ip -p 80 -t 30
Verify your setup
# Check if traffic shaping is active
sudo tc qdisc show dev eth0
Verify HTB classes are configured
sudo tc class show dev eth0 | grep htb
Check filter rules
sudo tc filter show dev eth0
Monitor real-time statistics
sudo tc -s class show dev eth0
Verify service is running
sudo systemctl status traffic-shaping.service
Test with actual traffic
sudo /usr/local/bin/monitor-traffic.sh
Advanced QoS policies
Configure application-specific shaping
Set up traffic shaping for specific applications using iptables MARK targets and tc filters.
# Mark packets from specific applications
sudo iptables -t mangle -A OUTPUT -p tcp --dport 3306 -j MARK --set-mark 10 # MySQL
sudo iptables -t mangle -A OUTPUT -p tcp --dport 5432 -j MARK --set-mark 10 # PostgreSQL
sudo iptables -t mangle -A OUTPUT -p tcp --dport 25 -j MARK --set-mark 30 # SMTP (low priority)
Create filters based on packet marks
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 handle 10 fw flowid 1:10 # High priority
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 3 handle 30 fw flowid 1:30 # Low priority
Implement time-based QoS policies
Create a script to adjust bandwidth allocation based on time of day for business hours optimization.
#!/bin/bash
INTERFACE=eth0
HOUR=$(date +%H)
Clear existing classes
tc class del dev $INTERFACE classid 1:10 2>/dev/null || true
tc class del dev $INTERFACE classid 1:20 2>/dev/null || true
tc class del dev $INTERFACE classid 1:30 2>/dev/null || true
if [ $HOUR -ge 9 ] && [ $HOUR -le 17 ]; then
# Business hours: prioritize work traffic
tc class add dev $INTERFACE parent 1:1 classid 1:10 htb rate 6mbit ceil 8mbit prio 1
tc class add dev $INTERFACE parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit prio 2
tc class add dev $INTERFACE parent 1:1 classid 1:30 htb rate 1mbit ceil 4mbit prio 3
else
# Off hours: more balanced allocation
tc class add dev $INTERFACE parent 1:1 classid 1:10 htb rate 4mbit ceil 6mbit prio 1
tc class add dev $INTERFACE parent 1:1 classid 1:20 htb rate 3mbit ceil 7mbit prio 2
tc class add dev $INTERFACE parent 1:1 classid 1:30 htb rate 3mbit ceil 8mbit prio 3
fi
echo "QoS policies updated for $(date)"
sudo chmod 755 /usr/local/bin/dynamic-qos.sh
Add to crontab to run every hour
echo "0 /usr/local/bin/dynamic-qos.sh" | sudo crontab -
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| tc command not found | iproute2 package not installed | sudo apt install iproute2 or sudo dnf install iproute |
| RTNETLINK answers: File exists | Qdisc already exists on interface | sudo tc qdisc del dev eth0 root then recreate |
| Traffic shaping not working | Wrong interface name specified | Check with ip link show and update scripts |
| Ingress shaping fails | IFB module not loaded | sudo modprobe ifb and echo ifb >> /etc/modules |
| Rules lost after reboot | No persistence configured | Enable systemd service: sudo systemctl enable traffic-shaping.service |
| Filter rules not matching | Incorrect syntax or precedence | Check with tc filter show dev eth0 and verify u32 match syntax |
| Bandwidth limits too restrictive | Rate and ceil values too low | Increase values and monitor with tc -s class show |
| High priority traffic throttled | Parent class rate limit reached | Increase parent class rate or adjust child class allocation |
Next steps
- Optimize Linux network stack performance with sysctl tuning and TCP congestion control
- Configure network load balancing with keepalived and VRRP for high availability failover
- Configure advanced iptables QoS with DSCP marking and traffic classification
- Implement network monitoring with ntopng for comprehensive traffic analysis
- Set up Squid proxy with bandwidth controls and content filtering
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
INTERFACE=""
TOTAL_BW="10mbit"
HIGH_RATE="4mbit"
HIGH_CEIL="6mbit"
MEDIUM_RATE="3mbit"
MEDIUM_CEIL="8mbit"
LOW_RATE="3mbit"
LOW_CEIL="10mbit"
# Usage function
usage() {
echo "Usage: $0 [-i interface] [-b total_bandwidth] [-h]"
echo " -i Network interface (auto-detected if not specified)"
echo " -b Total bandwidth limit (default: 10mbit)"
echo " -h Show this help message"
echo ""
echo "Example: $0 -i eth0 -b 100mbit"
exit 1
}
# Error handling
error_exit() {
echo -e "${RED}Error: $1${NC}" >&2
exit 1
}
success_msg() {
echo -e "${GREEN}$1${NC}"
}
warning_msg() {
echo -e "${YELLOW}$1${NC}"
}
# Cleanup function for rollback
cleanup() {
if [ -n "$INTERFACE" ]; then
warning_msg "Cleaning up traffic control configuration..."
tc qdisc del dev "$INTERFACE" root 2>/dev/null || true
fi
}
# Set trap for cleanup on error
trap cleanup ERR
# Parse command line arguments
while getopts "i:b:h" opt; do
case $opt in
i) INTERFACE="$OPTARG" ;;
b) TOTAL_BW="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
# Check if running as root
if [ "$EUID" -ne 0 ]; then
error_exit "This script must be run as root or with sudo"
fi
echo "[1/8] Detecting operating system..."
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
;;
*)
error_exit "Unsupported distribution: $ID"
;;
esac
else
error_exit "Cannot detect operating system"
fi
success_msg "Detected: $PRETTY_NAME"
echo "[2/8] Installing traffic control utilities..."
# Update package manager
$PKG_UPDATE
# Install required packages based on distribution
case "$PKG_MGR" in
apt)
$PKG_INSTALL iproute2 tc
;;
dnf|yum)
$PKG_INSTALL iproute tc
;;
esac
success_msg "Traffic control utilities installed"
echo "[3/8] Identifying network interface..."
# Auto-detect interface if not provided
if [ -z "$INTERFACE" ]; then
INTERFACE=$(ip route | grep default | awk '{print $5}' | head -n1)
if [ -z "$INTERFACE" ]; then
error_exit "Could not auto-detect network interface. Please specify with -i option"
fi
fi
# Verify interface exists
if ! ip link show "$INTERFACE" >/dev/null 2>&1; then
error_exit "Network interface '$INTERFACE' not found"
fi
success_msg "Using interface: $INTERFACE"
echo "[4/8] Removing existing traffic control configuration..."
# Clear existing qdisc
tc qdisc del dev "$INTERFACE" root 2>/dev/null || true
# Verify removal
if tc qdisc show dev "$INTERFACE" | grep -q htb; then
warning_msg "Some HTB configuration may still exist"
fi
success_msg "Existing configuration cleared"
echo "[5/8] Creating root HTB queueing discipline..."
# Create root HTB qdisc with total bandwidth limit
tc qdisc add dev "$INTERFACE" root handle 1: htb default 30
tc class add dev "$INTERFACE" parent 1: classid 1:1 htb rate "$TOTAL_BW"
success_msg "Root HTB queueing discipline created with ${TOTAL_BW} total bandwidth"
echo "[6/8] Creating traffic classes with priorities..."
# High priority class (SSH, VoIP, critical services)
tc class add dev "$INTERFACE" parent 1:1 classid 1:10 htb rate "$HIGH_RATE" ceil "$HIGH_CEIL" prio 1
# Medium priority class (HTTP/HTTPS, email)
tc class add dev "$INTERFACE" parent 1:1 classid 1:20 htb rate "$MEDIUM_RATE" ceil "$MEDIUM_CEIL" prio 2
# Low priority class (bulk transfers, P2P)
tc class add dev "$INTERFACE" parent 1:1 classid 1:30 htb rate "$LOW_RATE" ceil "$LOW_CEIL" prio 3
success_msg "Traffic classes created: High ($HIGH_RATE), Medium ($MEDIUM_RATE), Low ($LOW_RATE)"
echo "[7/8] Configuring fair queueing and traffic filters..."
# Add SFQ to each class for fair bandwidth distribution
tc qdisc add dev "$INTERFACE" parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev "$INTERFACE" parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev "$INTERFACE" parent 1:30 handle 30: sfq perturb 10
# SSH traffic - high priority
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 1 u32 match ip dport 22 0xffff flowid 1:10
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 1 u32 match ip sport 22 0xffff flowid 1:10
# HTTP/HTTPS traffic - medium priority
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 2 u32 match ip dport 80 0xffff flowid 1:20
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 2 u32 match ip dport 443 0xffff flowid 1:20
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 2 u32 match ip sport 80 0xffff flowid 1:20
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 2 u32 match ip sport 443 0xffff flowid 1:20
# SMTP/IMAP/POP3 - medium priority
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 2 u32 match ip dport 25 0xffff flowid 1:20
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 2 u32 match ip dport 587 0xffff flowid 1:20
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 2 u32 match ip dport 993 0xffff flowid 1:20
# FTP and P2P - low priority
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 3 u32 match ip dport 21 0xffff flowid 1:30
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 3 u32 match ip dport 6881 0xffff flowid 1:30
# DSCP-based classification for enterprise environments
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 1 u32 match ip tos 0xb8 0xfc flowid 1:10 # EF
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 2 u32 match ip tos 0x68 0xfc flowid 1:20 # AF31
success_msg "Traffic classification filters configured"
echo "[8/8] Verifying configuration..."
# Verify HTB configuration
if ! tc qdisc show dev "$INTERFACE" | grep -q "htb"; then
error_exit "HTB qdisc not properly configured"
fi
# Verify classes exist
CLASSES=$(tc class show dev "$INTERFACE" | grep -c "htb")
if [ "$CLASSES" -lt 4 ]; then
error_exit "Not all HTB classes were created properly"
fi
# Verify filters exist
FILTERS=$(tc filter show dev "$INTERFACE" | grep -c "flowid")
if [ "$FILTERS" -lt 5 ]; then
warning_msg "Some traffic filters may not have been applied"
fi
# Clear the error trap since we succeeded
trap - ERR
success_msg "Traffic shaping configuration completed successfully!"
echo ""
echo "Configuration Summary:"
echo " Interface: $INTERFACE"
echo " Total Bandwidth: $TOTAL_BW"
echo " High Priority: $HIGH_RATE (SSH, VoIP, EF DSCP)"
echo " Medium Priority: $MEDIUM_RATE (HTTP/HTTPS, Email, AF31 DSCP)"
echo " Low Priority: $LOW_RATE (FTP, P2P, Default)"
echo ""
echo "To view current configuration:"
echo " tc qdisc show dev $INTERFACE"
echo " tc class show dev $INTERFACE"
echo " tc filter show dev $INTERFACE"
echo ""
echo "To remove this configuration:"
echo " tc qdisc del dev $INTERFACE root"
Review the script before running. Execute with: bash install.sh