Master Linux Traffic Control (tc): Bandwidth Limiting, Latency Emulation, and Packet Loss with Netem and HTB

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Real-World Problem: The “Works on My Machine” Network Mirage

Everything runs cleanly in staging. Microservices trade gRPC calls in sub-millisecond round trips, Postgres queries complete in 2ms, and test workers report zero dropped packets. Then you deploy to production across edge locations, hybrid cloud clusters, and remote mobile clients.

Immediately, latency-sensitive workflows degrade. Requests start timing out after 5,000ms, throughput collapses, and engineers scramble to reproduce connection drops that vanish during local testing. Replicating flaky WAN links or saturated uplinks is near impossible when test containers talk over a pristine 10 Gbps loopback interface.

Teams usually reach for software mocks or scatter time.Sleep(100ms) calls across application code. But code-level delays cannot replicate kernel realities. They completely bypass TCP retransmission timers, Selective Acknowledgement (SACK) negotiations, socket buffer exhaustion, and intermediate router queue bloat.

Root Cause Analysis: Why Kernel-Level Packet Handling Matters

Network gremlins rarely stem from application code alone. Instead, they happen because the Linux networking stack buckles under real-world link conditions. When an uplink saturates or drops frames, the kernel decides how to buffer, order, and discard outgoing packets using queuing disciplines (qdiscs).

By default, modern Linux distributions attach work-conserving qdiscs like fq_codel or pfifo_fast to network interfaces. These defaults optimize for fair, low-latency delivery under normal loads, which hides production failure modes during testing:

  • No packet drop handling: Local integration tests never trigger TCP Fast Retransmit, tail drops, or exponential backoffs.
  • Zero latency jitter: Local ping times stay rock-solid at 0.05ms, masking race conditions sparked by out-of-order packet arrival.
  • Unrestricted egress bursts: A single runaway backup job can saturate outbound bandwidth at 950 Mbps, starving critical Redis heartbeats and SSH sessions.

To expose these edge cases and enforce strict multi-tenant bandwidth guarantees, you must shape packets in the kernel before they ever reach the physical NIC ring buffer.

Solutions Compared: Application Mocks vs. IPTables vs. Linux TC

Engineers generally handle network degradation using three distinct tools:

1. Application-Layer Proxies (Toxiproxy, Chaos Mesh, Custom Interceptors)

These tools sit between services in user-space to drop TCP streams or delay payloads.

  • Pros: Simple per-route configuration; convenient for Docker Compose integration tests.
  • Cons: Heavy memory and CPU overhead. They cannot inspect raw UDP/ICMP traffic cleanly and bypass the operating system’s native socket buffers.

2. Netfilter / IPTables Drop Rules

Operators frequently simulate line problems by dropping packets with iptables -m statistic --mode random --probability 0.05 -j DROP.

  • Pros: Built straight into Netfilter without installing additional packages.
  • Cons: A blunt sledgehammer. IPTables drops packets immediately, but it cannot inject transmission delays, simulate millisecond jitter, or shape traffic smoothly.

3. Linux Traffic Control (tc) with Netem and HTB

This native subsystem lives inside the Linux kernel networking stack, managing egress scheduling, classification, and rate shaping.

  • Pros: Zero user-space overhead. It works transparently across all system processes, handles exact statistical latency curves, and enforces multi-tier bandwidth caps down to the kilobit.
  • Cons: Terse command syntax and a steep learning curve.

Best Approach: Practical Traffic Control with Netem and HTB

The cleanest approach relies on Traffic Control (tc) through two core kernel modules: Netem (Network Emulator) for simulating dirty links, and HTB (Hierarchy Token Bucket) for rigid bandwidth allocation.

Inspecting Your Target Interface

First, verify your primary interface name and view existing active qdiscs:

ip link show
tc qdisc show dev eth0

Simulating Network Jitter, Delay, and Packet Loss with Netem

Netem lets you test distributed services under the harsh conditions of congested cellular links or transatlantic fiber routes.

Adding Fixed Latency

Inject a baseline 100ms round-trip penalty into every outbound packet on eth0:

sudo tc qdisc add dev eth0 root netem delay 100ms

Adding Latency with Realistic Jitter and Distribution

Real-world connections drift constantly. To simulate a 100ms base delay with ±20ms jitter distributed along a standard Gaussian curve:

sudo tc qdisc change dev eth0 root netem delay 100ms 20ms distribution normal

Simulating Packet Loss and Corruption

Drop 5% of outgoing packets at random while duplicating 1% of frames to test deduplication guards:

sudo tc qdisc change dev eth0 root netem loss 5% duplicate 1%

Checking the Applied Emulation

Verify live packet counters and dropped frame metrics:

tc -s qdisc show dev eth0

Resetting Netem Rules

Once testing finishes, remove the root qdisc to bring your interface back to default settings:

sudo tc qdisc del dev eth0 root

Restricting Bandwidth with Hierarchy Token Bucket (HTB)

Uncapped background jobs can easily saturate your outbound pipeline. An unthrottled S3 database upload pulling 100 Mbps will quickly starve real-time REST and GraphQL requests. HTB prevents this starvation by structuring bandwidth into parent-child class hierarchies with strict burst allowances and floor guarantees.

In our benchmarks on an Ubuntu 22.04 LTS instance, carving out explicit rate tiers slashed p99 API response latencies from 1,240ms back down to 42ms during peak nightly backups.

Step 1: Create the Root HTB Qdisc

Assign HTB as the root queuing discipline and route unclassified traffic to fallback class 1:20:

sudo tc qdisc add dev eth0 root handle 1: htb default 20

Step 2: Define the Root Bandwidth Pool

Declare your total available outbound bandwidth ceiling. In this example, we assume a standard 100 Mbps uplink:

sudo tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit ceil 100mbit

Step 3: Create Partitioned Sub-Classes

Carve the 100 Mbps pool into dedicated lanes for critical HTTP traffic and low-priority sync pipelines:

# Class 1:10 - Priority API Traffic (Floor: 80 Mbps, Burst limit: 100 Mbps, Priority: 1)
sudo tc class add dev eth0 parent 1:1 classid 1:10 htb rate 80mbit ceil 100mbit prio 1

# Class 1:20 - Background Sync Jobs (Floor: 10 Mbps, Hard ceiling: 20 Mbps, Priority: 2)
sudo tc class add dev eth0 parent 1:1 classid 1:20 htb rate 10mbit ceil 20mbit prio 2

Step 4: Route Traffic Using Filters

Map outgoing packets to specific classes based on destination port. The following filters forward standard web traffic (ports 443 and 80) to priority class 1:10:

sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
    match ip dport 443 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

Everything else—including batch S3 backups, log shippers, and file exports—routes into class 1:20, capping those transfers at 20 Mbps.

Verifying and Monitoring Active Traffic

Monitor token consumption, dropped packets, and current transfer rates in real time:

tc -s class show dev eth0

Run a throughput test using iperf3 -c <target-ip> -p 5201. You will watch unclassified transfers hit the 20 Mbps limit while web traffic on port 443 bursts cleanly to 100 Mbps.

Cleaning Up

To tear down all class hierarchies, token buckets, and filters, delete the root qdisc:

sudo tc qdisc del dev eth0 root

Summary Checklist

  • Simulating network chaos? Run tc qdisc add dev <interface> root netem delay 80ms 15ms loss 3% to test latency, jitter, and dropped frames directly in the kernel stack.
  • Preventing outbound link saturation? Configure tc qdisc add dev <interface> root handle 1: htb to set guaranteed bandwidth floors and hard caps.
  • Wrapping up your tests? Always run tc qdisc del dev <interface> root to avoid leaving artificial bottlenecks running in staging or production environments.
Share: