Skip to content

ISP Subscriber Throttling

This guide explains how RouterOS implements the token bucket algorithm for traffic shaping, how burst parameters interact, and how to build per-subscriber rate limiting for residential and business ISP deployments.

Prerequisites: Queue Types, Simple Queue, Queue Tree.


RouterOS queue shaping is based on Hierarchical Token Bucket (HTB). Understanding the underlying model helps you predict queue behaviour under load.

A token bucket controls how much traffic a queue can transmit. Tokens accumulate in the bucket at a steady rate (the configured rate limit) up to a maximum capacity. Each byte transmitted consumes one token. When the bucket is empty, packets must wait until enough tokens accumulate.

Token rate: 10 Mbps
Bucket capacity: determines how long a burst can last
Time → 0s 1s 2s 3s
Tokens: FULL → drained → refilling → FULL
Traffic: idle → burst → steady → idle

RouterOS does not use a classical token bucket with explicit capacity. Instead it tracks a rolling average rate over the burst-time window:

average_rate = exponential moving average of throughput over burst-time
  1. When average_rate < burst-threshold → queue can transmit up to burst-limit
  2. When average_rate ≥ burst-threshold → queue is capped at max-limit
  3. When burst is inactive, rate never exceeds max-limit

The burst-time window is the averaging period — longer windows mean the router needs more sustained low-traffic time before burst reactivates.

Parameter mapping to token bucket concepts

Section titled “Parameter mapping to token bucket concepts”
Token Bucket ConceptRouterOS ParameterEffect
Token refill rate (normal)max-limitSteady-state ceiling
Committed Information Ratelimit-atGuaranteed minimum
Bucket capacity / peak rateburst-limitMaximum burst speed
Bucket fill thresholdburst-thresholdActivates/deactivates burst
Averaging windowburst-timeHow long the average is measured

These two parameters are frequently confused. They serve different roles.

burst-limit sets the maximum rate a queue can reach when burst is active. It is always higher than max-limit. If burst-limit = max-limit, burst has no visible effect.

burst-limit=20M # queue can burst up to 20 Mbps
max-limit=10M # normal steady-state cap is 10 Mbps

burst-threshold is the average rate at which burst switches off. When the rolling average exceeds this value, the queue drops from burst-limit back to max-limit.

burst-threshold=8M # burst deactivates when average rate hits 8 Mbps

For burst to be useful, the threshold must be below max-limit. Otherwise, normal traffic already exceeds the threshold and burst is permanently inactive.

Rule of thumb: set burst-threshold to 70–85% of max-limit.

max-limit=10M # normal cap
burst-limit=20M # double speed when bursting
burst-threshold=8M # 80% of max-limit
burst-time=16s # average over 16 seconds

Behaviour timeline:

PhaseAverage rateEffective capDuration
Idle then page load< 8 Mbps20 Mbps (burst active)~6–8s
Sustained downloadrising → 8 Mbpsdrops to 10 Mbps
Download continues≥ 8 Mbps10 Mbps (burst off)steady
Idle againfalls below 8 Mbps20 Mbps (burst reactivates)after ~8s
MistakeResult
burst-threshold ≥ burst-limitBurst never activates
burst-threshold ≤ 0 (or unset)Burst disabled entirely
burst-threshold > max-limitBurst always active — subscriber always at burst-limit
burst-time too short (e.g., 4s)Burst cycles on/off rapidly; annoying for TCP
burst-limit = max-limitNo burst benefit; wastes configuration

Method 1: Simple Queue (small deployments, < 200 subscribers)

Section titled “Method 1: Simple Queue (small deployments, < 200 subscribers)”

Simple Queue creates one queue entry per subscriber. Suitable when subscriber counts are small and IP addresses are static or predictable.

Basic subscriber queue:

/queue simple
add name=sub-192.168.1.10 target=192.168.1.10/32 \
max-limit=10M/5M \
burst-limit=20M/10M \
burst-threshold=8M/4M \
burst-time=16s/16s \
comment="Residential 10M/5M"

Applying to a range of subscribers with a catch-all:

/queue simple
# Individual subscriber queues first
add name=sub-10 target=192.168.1.10/32 max-limit=20M/10M \
burst-limit=40M/20M burst-threshold=16M/8M burst-time=16s/16s
add name=sub-11 target=192.168.1.11/32 max-limit=10M/5M \
burst-limit=20M/10M burst-threshold=8M/4M burst-time=16s/16s
# Catch-all for unmanaged IPs — throttle hard
add name=unmanaged target=192.168.1.0/24 max-limit=2M/1M

Order matters: RouterOS evaluates Simple Queue entries top-to-bottom. Place specific host queues before subnet catch-alls.

Method 2: Queue Tree + PCQ (large deployments)

Section titled “Method 2: Queue Tree + PCQ (large deployments)”

PCQ (Per Connection Queue) creates dynamic per-IP sub-queues automatically. No per-subscriber queue entries needed — one queue rule handles all subscribers.

Step 1 — Define PCQ queue types:

/queue type
add name=pcq-sub-down kind=pcq \
pcq-classifier=dst-address \
pcq-rate=10M \
pcq-limit=100
add name=pcq-sub-up kind=pcq \
pcq-classifier=src-address \
pcq-rate=5M \
pcq-limit=100

Step 2 — Mark traffic in Mangle:

/ip firewall mangle
# Download: mark packets destined for LAN
add chain=forward action=mark-packet new-packet-mark=sub-down passthrough=no \
out-interface=bridge-LAN
# Upload: mark packets leaving LAN
add chain=forward action=mark-packet new-packet-mark=sub-up passthrough=no \
in-interface=bridge-LAN

Step 3 — Attach PCQ queues to Queue Tree:

/queue tree
add name=Subscriber-Down parent=global packet-mark=sub-down \
queue=pcq-sub-down max-limit=1000M
add name=Subscriber-Up parent=global packet-mark=sub-up \
queue=pcq-sub-up max-limit=500M

Each unique dst-address (download) or src-address (upload) gets its own dynamic sub-queue capped at 10 Mbps down / 5 Mbps up.


Example 1: Residential plan — 25M/10M with short burst

Section titled “Example 1: Residential plan — 25M/10M with short burst”

Standard residential broadband. Give subscribers a fast burst for page loads and small downloads, then throttle to the plan rate.

/queue simple
add name=residential-template target=192.168.100.0/24 \
max-limit=25M/10M \
burst-limit=50M/20M \
burst-threshold=20M/8M \
burst-time=8s/8s \
comment="25M/10M residential — burst 50M/20M for 8s"

Short burst-time=8s means burst deactivates quickly after a download starts — subscribers get a fast start but not sustained double-speed for long transfers.

Example 2: Business plan — 50M/25M with guaranteed minimum

Section titled “Example 2: Business plan — 50M/25M with guaranteed minimum”

Business subscribers need predictable performance. Use limit-at to guarantee a minimum even when the link is congested.

/queue tree
# Mangle marks business traffic as biz-down / biz-up
add name=Business-Down parent=global packet-mark=biz-down \
limit-at=20M max-limit=50M \
burst-limit=75M burst-threshold=40M burst-time=30s \
priority=3
add name=Business-Up parent=global packet-mark=biz-up \
limit-at=10M max-limit=25M \
burst-limit=40M burst-threshold=20M burst-time=30s \
priority=3

limit-at=20M ensures the queue receives at least 20 Mbps when congestion causes parent bandwidth contention. priority=3 is higher than residential (6), so business traffic is scheduled first.

Example 3: Throttled / over-quota subscribers

Section titled “Example 3: Throttled / over-quota subscribers”

After a subscriber exhausts a monthly data quota, reduce them to a low speed without disconnecting. Use address lists to mark throttled subscribers.

# Add to throttle list via script or manual action
/ip firewall address-list
add list=throttled address=192.168.1.55 comment="quota exceeded 2026-03-23"
# Mangle: mark throttled subscriber traffic
/ip firewall mangle
add chain=forward action=mark-connection \
new-connection-mark=throttled-conn passthrough=yes \
src-address-list=throttled
add chain=forward action=mark-connection \
new-connection-mark=throttled-conn passthrough=yes \
dst-address-list=throttled
add chain=forward action=mark-packet \
new-packet-mark=throttled passthrough=no \
connection-mark=throttled-conn
# Queue: cap throttled subscribers at 1M/512k
/queue tree
add name=Throttled parent=global packet-mark=throttled \
max-limit=1M priority=8 comment="over-quota throttle"

To restore a subscriber, remove them from the address list:

/ip firewall address-list remove [find list=throttled address=192.168.1.55]

Example 4: Time-based throttling (night vs day rates)

Section titled “Example 4: Time-based throttling (night vs day rates)”

Use a script with scheduler to swap queue limits for off-peak hours.

# Script: apply night-rate (uncapped, or higher burst)
/system script
add name=apply-night-rate source={
/queue simple set [find name=residential-template] \
max-limit=50M/25M burst-limit=100M/50M burst-threshold=40M/20M
}
# Script: apply day-rate (normal plan limits)
add name=apply-day-rate source={
/queue simple set [find name=residential-template] \
max-limit=25M/10M burst-limit=50M/20M burst-threshold=20M/8M
}
/system scheduler
add name=night-rate on-event=apply-night-rate start-time=22:00:00 interval=1d
add name=day-rate on-event=apply-day-rate start-time=06:00:00 interval=1d

Choosing Simple Queue vs Queue Tree for Subscribers

Section titled “Choosing Simple Queue vs Queue Tree for Subscribers”
FactorSimple QueueQueue Tree + PCQ
Subscriber count< 200200+
Dynamic IPs (DHCP)Manual or script-drivenAutomatic via PCQ classifier
Per-subscriber burstPer-entry configurationPCQ does not support burst per sub-queue
Hierarchy / tiersFlat onlyFull HTB hierarchy
VisibilityPer-queue stats per subscriberAggregate stats; per-IP via /tool torch

For large deployments with dynamic IPs, combine Queue Tree + PCQ for the rate cap and Mangle + address-lists for tier classification.


# Check queue throughput and drops in real time
/queue simple print stats
# Watch per-subscriber PCQ sub-queues
/queue tree print stats
# Identify top talkers on an interface
/tool torch interface=ether1 src-address=0.0.0.0/0 duration=10

Subscriber exceeds max-limit consistently

  • Verify FastTrack is not bypassing the queue. FastTrack-ed connections skip queue processing. Either disable FastTrack on that interface or add a Mangle passthrough rule for subscribers before the FastTrack rule.

Burst never activates

  • Check burst-threshold < max-limit. If threshold ≥ max-limit, burst stays off.
  • Confirm burst-limit > max-limit.
  • Verify the subscriber’s current average rate is below burst-threshold using /queue simple print stats — look at the rate column.

PCQ appears to give unequal shares

  • Confirm pcq-classifier=dst-address for download and src-address for upload. Reversed classifiers break sub-queue creation.
  • Connections behind NAT share one source IP — PCQ sees them as one subscriber. For NAT environments, use Simple Queue per subscriber address instead.

Throttle queue not affecting subscriber

  • Check Mangle rule order. Connection-mark rules must fire before the existing mark-packet rules (or use passthrough=yes and chain order carefully).
  • Confirm packet-mark name matches between Mangle and Queue Tree exactly (case-sensitive).