Learn how to implement advanced network traffic control on Linux using tc (traffic control) and HTB (Hierarchical Token Bucket) queueing disciplines. This tutorial covers bandwidth limiting, QoS policies, and traffic prioritization for optimal network performance.
Prerequisites
- Root or sudo access
- Basic networking knowledge
- Understanding of Linux command line
What this solves
Network traffic shaping controls bandwidth usage and prioritizes different types of traffic on your Linux server or gateway. This prevents any single application or user from consuming all available bandwidth while ensuring critical services get the resources they need.
Step-by-step configuration
Install traffic control tools
Install the iproute2 package which contains the tc command and related traffic shaping utilities.
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 eth0, ens3, or similar.
ip link show
ip addr show
Create root queueing discipline
Set up the HTB (Hierarchical Token Bucket) root queueing discipline on your network interface. Replace eth0 with your actual interface name.
sudo tc qdisc add dev eth0 root handle 1: htb default 30
This creates a root queueing discipline with handle 1: and sets the default class to 30 for unclassified traffic.
Configure bandwidth classes
Create hierarchical traffic classes with different bandwidth allocations. This example creates a 100Mbit total with three priority levels.
# Root class - total bandwidth limit
sudo tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit
High priority class - 50% of bandwidth, can burst to 80%
sudo tc class add dev eth0 parent 1:1 classid 1:10 htb rate 50mbit ceil 80mbit prio 1
Medium priority class - 30% of bandwidth, can burst to 60%
sudo tc class add dev eth0 parent 1:1 classid 1:20 htb rate 30mbit ceil 60mbit prio 2
Low priority class - 20% of bandwidth, can burst to 40%
sudo tc class add dev eth0 parent 1:1 classid 1:30 htb rate 20mbit ceil 40mbit prio 3
Add leaf queueing disciplines
Attach leaf queueing disciplines to each class for packet scheduling. SFQ (Stochastic Fair Queueing) ensures fair distribution within each 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 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
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
SMTP traffic (port 25) - high priority
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 25 0xffff flowid 1:10
FTP traffic (port 21) - low priority
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip dport 21 0xffff flowid 1:30
Configure IP-based traffic shaping
Apply different bandwidth limits to specific IP addresses or subnets for more granular control.
# Limit specific IP to 10Mbit
sudo tc class add dev eth0 parent 1:1 classid 1:40 htb rate 10mbit ceil 15mbit
sudo tc qdisc add dev eth0 parent 1:40 handle 40: sfq perturb 10
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 4 u32 match ip src 192.168.1.100/32 flowid 1:40
Limit subnet to 20Mbit total
sudo tc class add dev eth0 parent 1:1 classid 1:50 htb rate 20mbit ceil 25mbit
sudo tc qdisc add dev eth0 parent 1:50 handle 50: sfq perturb 10
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 5 u32 match ip src 192.168.2.0/24 flowid 1:50
Add DSCP-based QoS classification
Configure traffic shaping based on DSCP (Differentiated Services Code Point) markings for enterprise QoS compliance.
# Voice traffic (DSCP EF - 46) - highest priority
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip tos 0xb8 0xfc flowid 1:10
Video traffic (DSCP AF41 - 34) - high priority
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip tos 0x88 0xfc flowid 1:10
Best effort traffic (DSCP 0) - 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 traffic shaping startup script
Create a script to automatically apply traffic shaping rules at boot time.
#!/bin/bash
Network interface
INTERFACE="eth0"
Remove existing rules
tc qdisc del dev $INTERFACE root 2>/dev/null
Create root qdisc
tc qdisc add dev $INTERFACE root handle 1: htb default 30
Root class
tc class add dev $INTERFACE parent 1: classid 1:1 htb rate 100mbit
Priority classes
tc class add dev $INTERFACE parent 1:1 classid 1:10 htb rate 50mbit ceil 80mbit prio 1
tc class add dev $INTERFACE parent 1:1 classid 1:20 htb rate 30mbit ceil 60mbit prio 2
tc class add dev $INTERFACE parent 1:1 classid 1:30 htb rate 20mbit ceil 40mbit prio 3
Leaf qdiscs
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
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 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
echo "Traffic shaping configured successfully"
sudo chmod +x /usr/local/bin/setup-traffic-shaping.sh
Create systemd service
Create a systemd service to automatically apply traffic shaping at startup.
[Unit]
Description=Network Traffic Shaping
After=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
Configure ingress traffic control
Apply traffic shaping to incoming traffic using the ingress queueing discipline with policing.
# Add ingress qdisc
sudo tc qdisc add dev eth0 ingress
Police incoming traffic to 50Mbit
sudo tc filter add dev eth0 parent ffff: protocol ip prio 1 u32 match u32 0 0 police rate 50mbit burst 10k drop
Police specific source IP to 10Mbit
sudo tc filter add dev eth0 parent ffff: protocol ip prio 2 u32 match ip src 192.168.1.100/32 police rate 10mbit burst 5k drop
Monitor and troubleshoot traffic shaping rules
View current queueing disciplines
Check the currently configured queueing disciplines and their status.
tc qdisc show dev eth0
tc qdisc show
Display traffic classes and statistics
Monitor bandwidth usage and packet statistics for each traffic class.
# Show classes with statistics
tc -s class show dev eth0
Show detailed class information
tc class show dev eth0
View active filters
Check which filters are classifying traffic and their match statistics.
# Show all filters
tc filter show dev eth0
Show filters with statistics
tc -s filter show dev eth0
Monitor real-time traffic
Use watch command to monitor traffic shaping statistics in real-time.
watch -n 1 'tc -s class show dev eth0'
watch -n 2 'tc -s qdisc show dev eth0'
Remove traffic shaping rules
Clean up traffic shaping configuration when needed.
# Remove all egress rules
sudo tc qdisc del dev eth0 root
Remove ingress rules
sudo tc qdisc del dev eth0 ingress
Remove specific class
sudo tc class del dev eth0 classid 1:40
Advanced QoS configuration
Configure burst and ceil parameters
Fine-tune burst sizes and ceiling rates for optimal performance.
# Class with custom burst parameters
sudo tc class add dev eth0 parent 1:1 classid 1:60 htb rate 25mbit ceil 50mbit burst 15k cburst 8k prio 2
Add corresponding leaf qdisc
sudo tc qdisc add dev eth0 parent 1:60 handle 60: sfq perturb 10
Implement weighted fair queueing
Use different queueing disciplines for specific traffic types.
# Use PFIFO for low-latency traffic
sudo tc qdisc add dev eth0 parent 1:10 handle 10: pfifo limit 10
Use FQ_CODEL for general traffic
sudo tc qdisc add dev eth0 parent 1:20 handle 20: fq_codel
Verify your setup
# Check queueing disciplines
tc qdisc show dev eth0
Verify traffic classes
tc class show dev eth0
Check active filters
tc filter show dev eth0
Monitor statistics
tc -s class show dev eth0
Test bandwidth with iperf3
sudo apt install iperf3
iperf3 -c target_server_ip -t 30
Check if service is enabled
systemctl status traffic-shaping
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| tc: RTNETLINK answers: File exists | Qdisc already configured | sudo tc qdisc del dev eth0 root then reconfigure |
| Traffic not being limited | Wrong interface name | Check with ip link show and update interface name |
| Filter not matching traffic | Incorrect port or IP syntax | Verify with tc -s filter show dev eth0 and check packet counts |
| Rules lost after reboot | No automatic startup | Enable systemd service: sudo systemctl enable traffic-shaping |
| Ingress shaping not working | Missing ingress qdisc | Add ingress qdisc: sudo tc qdisc add dev eth0 ingress |
| High latency on priority traffic | Incorrect burst settings | Increase burst parameters or use different queueing discipline |
Next steps
- Set up network monitoring with SNMP and Grafana dashboards for traffic visualization
- Configure NGINX load balancing with health checks to distribute traffic efficiently
- Implement advanced iptables QoS with DSCP marking for packet classification
- Set up bandwidth monitoring with ntopng and InfluxDB for detailed traffic analysis
- Configure network traffic mirroring for analysis to capture and analyze network flows
Running this in production?
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' # No Color
# Default values
INTERFACE=""
TOTAL_BANDWIDTH="100mbit"
CLEANUP_ON_EXIT=false
# Usage message
usage() {
echo "Usage: $0 -i <interface> [-b <bandwidth>] [-c]"
echo " -i <interface> Network interface to configure (required)"
echo " -b <bandwidth> Total bandwidth limit (default: 100mbit)"
echo " -c Cleanup existing tc rules before applying new ones"
echo "Example: $0 -i eth0 -b 1gbit -c"
exit 1
}
# Parse arguments
while getopts "i:b:ch" opt; do
case $opt in
i) INTERFACE="$OPTARG" ;;
b) TOTAL_BANDWIDTH="$OPTARG" ;;
c) CLEANUP_ON_EXIT=true ;;
h) usage ;;
*) usage ;;
esac
done
# Validate required arguments
if [[ -z "$INTERFACE" ]]; then
echo -e "${RED}Error: Network interface is required${NC}"
usage
fi
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}Error: This script must be run as root or with sudo${NC}"
exit 1
fi
# Error cleanup function
cleanup_on_error() {
echo -e "${RED}Error occurred. Cleaning up...${NC}"
tc qdisc del dev "$INTERFACE" root 2>/dev/null || true
exit 1
}
trap cleanup_on_error ERR
# Auto-detect distribution
detect_distro() {
if [[ ! -f /etc/os-release ]]; then
echo -e "${RED}Error: Cannot detect distribution - /etc/os-release not found${NC}"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
TC_PACKAGE="iproute2"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
TC_PACKAGE="iproute-tc"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
TC_PACKAGE="iproute-tc"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
TC_PACKAGE="iproute-tc"
;;
*)
echo -e "${RED}Error: Unsupported distribution: $ID${NC}"
exit 1
;;
esac
}
# Verify network interface exists
verify_interface() {
if ! ip link show "$INTERFACE" >/dev/null 2>&1; then
echo -e "${RED}Error: Network interface '$INTERFACE' not found${NC}"
echo "Available interfaces:"
ip link show | grep -E "^[0-9]+:" | cut -d: -f2 | tr -d ' '
exit 1
fi
}
# Install required packages
install_packages() {
echo -e "${GREEN}[1/7] Installing traffic control tools...${NC}"
# Update package cache
$PKG_UPDATE >/dev/null 2>&1
# Install required packages
$PKG_INSTALL $TC_PACKAGE >/dev/null 2>&1
# Verify tc command is available
if ! command -v tc >/dev/null 2>&1; then
echo -e "${RED}Error: tc command not found after installation${NC}"
exit 1
fi
}
# Clean existing rules if requested
cleanup_existing() {
if [[ "$CLEANUP_ON_EXIT" == true ]]; then
echo -e "${YELLOW}[2/7] Cleaning existing tc rules...${NC}"
tc qdisc del dev "$INTERFACE" root 2>/dev/null || true
tc qdisc del dev "$INTERFACE" ingress 2>/dev/null || true
else
echo -e "${GREEN}[2/7] Skipping cleanup (use -c to clean existing rules)${NC}"
fi
}
# Create root queueing discipline
setup_root_qdisc() {
echo -e "${GREEN}[3/7] Setting up root queueing discipline...${NC}"
# Create HTB root qdisc with default class 30
tc qdisc add dev "$INTERFACE" root handle 1: htb default 30
}
# Configure bandwidth classes
setup_classes() {
echo -e "${GREEN}[4/7] Configuring bandwidth classes...${NC}"
# Root class - total bandwidth limit
tc class add dev "$INTERFACE" parent 1: classid 1:1 htb rate "$TOTAL_BANDWIDTH"
# Calculate bandwidth allocations based on percentages
case "$TOTAL_BANDWIDTH" in
*mbit)
BASE=$(echo "$TOTAL_BANDWIDTH" | sed 's/mbit//')
HIGH_RATE="${BASE%.*}mbit"
HIGH_CEIL=$(( (${BASE%.*} * 80) / 100 ))mbit
MED_RATE=$(( (${BASE%.*} * 30) / 100 ))mbit
MED_CEIL=$(( (${BASE%.*} * 60) / 100 ))mbit
LOW_RATE=$(( (${BASE%.*} * 20) / 100 ))mbit
LOW_CEIL=$(( (${BASE%.*} * 40) / 100 ))mbit
;;
*gbit)
HIGH_RATE="500mbit"
HIGH_CEIL="800mbit"
MED_RATE="300mbit"
MED_CEIL="600mbit"
LOW_RATE="200mbit"
LOW_CEIL="400mbit"
;;
*)
HIGH_RATE="50mbit"
HIGH_CEIL="80mbit"
MED_RATE="30mbit"
MED_CEIL="60mbit"
LOW_RATE="20mbit"
LOW_CEIL="40mbit"
;;
esac
# High priority class - 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 - normal services
tc class add dev "$INTERFACE" parent 1:1 classid 1:20 htb rate "$MED_RATE" ceil "$MED_CEIL" prio 2
# Low priority class - background traffic
tc class add dev "$INTERFACE" parent 1:1 classid 1:30 htb rate "$LOW_RATE" ceil "$LOW_CEIL" prio 3
}
# Add leaf queueing disciplines
setup_leaf_qdiscs() {
echo -e "${GREEN}[5/7] Adding leaf queueing disciplines...${NC}"
# Add SFQ (Stochastic Fair Queueing) to each class
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
}
# Create traffic classification filters
setup_filters() {
echo -e "${GREEN}[6/7] Setting up traffic classification filters...${NC}"
# SSH traffic (port 22) - 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
# DNS traffic (port 53) - high priority
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 1 u32 match ip dport 53 0xffff flowid 1:10
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 1 u32 match ip sport 53 0xffff flowid 1:10
# HTTP traffic (port 80) - 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 sport 80 0xffff flowid 1:20
# HTTPS traffic (port 443) - medium priority
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 443 0xffff flowid 1:20
# SMTP traffic (port 25) - high priority
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 1 u32 match ip dport 25 0xffff flowid 1:10
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 1 u32 match ip sport 25 0xffff flowid 1:10
# FTP traffic (port 21) - 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 sport 21 0xffff flowid 1:30
# DSCP-based classification
# Voice traffic (DSCP EF - 46) - highest priority
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 1 u32 match ip tos 0xb8 0xfc flowid 1:10
# Video traffic (DSCP AF41 - 34) - high priority
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 1 u32 match ip tos 0x88 0xfc flowid 1:10
}
# Verify configuration
verify_setup() {
echo -e "${GREEN}[7/7] Verifying traffic shaping configuration...${NC}"
# Check if qdisc is properly configured
if ! tc qdisc show dev "$INTERFACE" | grep -q "htb"; then
echo -e "${RED}Error: HTB qdisc not found on interface $INTERFACE${NC}"
return 1
fi
# Check if classes are configured
CLASS_COUNT=$(tc class show dev "$INTERFACE" | wc -l)
if [[ $CLASS_COUNT -lt 4 ]]; then
echo -e "${RED}Error: Expected at least 4 classes, found $CLASS_COUNT${NC}"
return 1
fi
# Check if filters are configured
FILTER_COUNT=$(tc filter show dev "$INTERFACE" | wc -l)
if [[ $FILTER_COUNT -lt 10 ]]; then
echo -e "${YELLOW}Warning: Expected at least 10 filters, found $FILTER_COUNT${NC}"
fi
echo -e "${GREEN}Traffic shaping successfully configured on $INTERFACE${NC}"
echo -e "${GREEN}Total bandwidth: $TOTAL_BANDWIDTH${NC}"
echo -e "${GREEN}Classes configured: $CLASS_COUNT${NC}"
echo -e "${GREEN}Filters configured: $FILTER_COUNT${NC}"
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 all traffic shaping:"
echo " tc qdisc del dev $INTERFACE root"
}
# Main execution
main() {
echo -e "${GREEN}Linux Traffic Shaping Setup${NC}"
echo "Interface: $INTERFACE"
echo "Bandwidth: $TOTAL_BANDWIDTH"
echo ""
detect_distro
verify_interface
install_packages
cleanup_existing
setup_root_qdisc
setup_classes
setup_leaf_qdiscs
setup_filters
verify_setup
echo -e "${GREEN}Traffic shaping setup completed successfully!${NC}"
}
# Disable error trap for main execution
trap - ERR
main "$@"
Review the script before running. Execute with: bash install.sh