Skip to content

HTB ISP Tiered Bandwidth

This guide shows how to build a RouterOS HTB queue tree for an ISP that offers tiered service plans — for example Bronze, Silver, and Gold — on a shared uplink. The design gives each tier a guaranteed aggregate rate, a burst allowance, and a hard ceiling. Within each tier, PCQ distributes bandwidth fairly across all active customers. VoIP traffic receives priority over data in every tier.

Prerequisites: Familiarity with Queue Tree fundamentals (HTB, limit-at, max-limit, priority) and PCQ classifier behaviour.


The queue tree has three levels:

QT-DOWN (root — total downlink, e.g. 1 Gbps)
├── DOWN-BRONZE limit-at=100M max-limit=300M priority=6
│ ├── DOWN-BRONZE-VOIP priority=1 (per-customer PCQ)
│ └── DOWN-BRONZE-DATA priority=6 (per-customer PCQ)
├── DOWN-SILVER limit-at=200M max-limit=500M priority=4
│ ├── DOWN-SILVER-VOIP priority=1
│ └── DOWN-SILVER-DATA priority=4
└── DOWN-GOLD limit-at=300M max-limit=800M priority=2
├── DOWN-GOLD-VOIP priority=1
└── DOWN-GOLD-DATA priority=2
QT-UP (root — total uplink)
└── (mirror structure for upload)

How the tiers interact:

  • When the 1 Gbps uplink is idle, Gold customers can use up to 800 Mbps.
  • As contention grows, each tier is guaranteed its limit-at before any borrowing occurs: Bronze 100 Mbps, Silver 200 Mbps, Gold 300 Mbps.
  • Within each tier, VoIP packets (priority 1) are served before data (priority 4–6) whenever the tier’s max-limit is reached.
  • PCQ at the leaf level divides each class fairly among active customers — no single customer can starve others in the same tier.

Create PCQ types for each tier. Set pcq-rate to the per-customer ceiling; set it to 0 for fully dynamic sharing.

/queue type
# Per-customer download caps by tier
add name=pcq-down-bronze kind=pcq pcq-classifier=dst-address pcq-rate=10M
add name=pcq-down-silver kind=pcq pcq-classifier=dst-address pcq-rate=25M
add name=pcq-down-gold kind=pcq pcq-classifier=dst-address pcq-rate=50M
# Per-customer upload caps by tier
add name=pcq-up-bronze kind=pcq pcq-classifier=src-address pcq-rate=2M
add name=pcq-up-silver kind=pcq pcq-classifier=src-address pcq-rate=5M
add name=pcq-up-gold kind=pcq pcq-classifier=src-address pcq-rate=10M
# VoIP uses dynamic sharing (no hard per-customer VoIP cap)
add name=pcq-down-voip kind=pcq pcq-classifier=dst-address pcq-rate=0
add name=pcq-up-voip kind=pcq pcq-classifier=src-address pcq-rate=0

Tip: pcq-rate=0 means all active substreams share the parent’s capacity equally. Use a non-zero value when you want a hard per-customer ceiling regardless of how much spare capacity exists.


Maintain address lists to classify customers into tiers. These are the only place you need to change when a customer upgrades or downgrades.

/ip firewall address-list
add address=203.0.113.10 list=bronze-customers
add address=203.0.113.11 list=bronze-customers
add address=203.0.113.20 list=silver-customers
add address=203.0.113.30 list=gold-customers

Mark packets by tier and by traffic class (VoIP vs data). Order matters: place VoIP rules above tier rules so VoIP packets receive both marks via passthrough=yes.

/ip firewall mangle
# --- VoIP identification (DSCP EF = 46, covers SIP and RTP) ---
add chain=forward dscp=46 action=mark-packet \
new-packet-mark=pm-voip-down passthrough=yes
add chain=forward dscp=46 action=mark-packet \
new-packet-mark=pm-voip-up passthrough=yes
# --- Download tier marks (traffic arriving at customer addresses) ---
add chain=forward dst-address-list=bronze-customers \
action=mark-packet new-packet-mark=pm-bronze-down passthrough=yes
add chain=forward dst-address-list=silver-customers \
action=mark-packet new-packet-mark=pm-silver-down passthrough=yes
add chain=forward dst-address-list=gold-customers \
action=mark-packet new-packet-mark=pm-gold-down passthrough=yes
# --- Upload tier marks (traffic leaving customer addresses) ---
add chain=forward src-address-list=bronze-customers \
action=mark-packet new-packet-mark=pm-bronze-up passthrough=yes
add chain=forward src-address-list=silver-customers \
action=mark-packet new-packet-mark=pm-silver-up passthrough=yes
add chain=forward src-address-list=gold-customers \
action=mark-packet new-packet-mark=pm-gold-up passthrough=yes

Important: Disable FastTrack for customer traffic, or FastTracked packets will bypass mangle and enter no queue:

/ip firewall filter
add chain=forward action=accept src-address-list=bronze-customers \
comment="allow but do not fasttrack — managed by QoS"

Alternatively, exclude customer subnets from the FastTrack connection mark.


Attach roots to global (postrouting) for upload and global-in (prerouting) for download. Adjust max-limit to match your actual uplink capacity.

/queue tree
add name=QT-DOWN parent=global-in max-limit=1G
add name=QT-UP parent=global max-limit=1G

Each tier queue sits under the root and sets the aggregate policy for that plan.

TierGuaranteed (down)Ceiling (down)Burst (down)Priority
Bronze100 Mbps300 Mbps350 Mbps6
Silver200 Mbps500 Mbps600 Mbps4
Gold300 Mbps800 Mbps900 Mbps2
/queue tree
# --- Download tiers ---
add name=DOWN-BRONZE parent=QT-DOWN packet-mark=pm-bronze-down \
limit-at=100M max-limit=300M \
burst-limit=350M burst-threshold=250M burst-time=30s \
priority=6
add name=DOWN-SILVER parent=QT-DOWN packet-mark=pm-silver-down \
limit-at=200M max-limit=500M \
burst-limit=600M burst-threshold=420M burst-time=30s \
priority=4
add name=DOWN-GOLD parent=QT-DOWN packet-mark=pm-gold-down \
limit-at=300M max-limit=800M \
burst-limit=900M burst-threshold=700M burst-time=30s \
priority=2
# --- Upload tiers ---
add name=UP-BRONZE parent=QT-UP packet-mark=pm-bronze-up \
limit-at=30M max-limit=100M \
burst-limit=120M burst-threshold=80M burst-time=30s \
priority=6
add name=UP-SILVER parent=QT-UP packet-mark=pm-silver-up \
limit-at=80M max-limit=250M \
burst-limit=300M burst-threshold=210M burst-time=30s \
priority=4
add name=UP-GOLD parent=QT-UP packet-mark=pm-gold-up \
limit-at=150M max-limit=400M \
burst-limit=480M burst-threshold=340M burst-time=30s \
priority=2

burst-time=30s is the averaging window. If a Bronze tier’s 30-second average download rate is below burst-threshold=250M, all Bronze customers collectively can burst to 350 Mbps. Once sustained traffic pushes the average above 250 Mbps, the tier drops back to its max-limit=300M.

Set burst-threshold roughly at 80–85% of max-limit to allow short spikes (page loads, connection setup) without letting bulk transfers hold burst for long.


Step 6: Service-Class Leaf Queues (VoIP Priority)

Section titled “Step 6: Service-Class Leaf Queues (VoIP Priority)”

Add VoIP and data children under each tier. VoIP uses priority=1 to be served first when the tier’s max-limit is reached.

/queue tree
# --- Bronze download children ---
add name=DOWN-BRONZE-VOIP parent=DOWN-BRONZE packet-mark=pm-voip-down \
queue=pcq-down-voip priority=1 limit-at=20M max-limit=80M
add name=DOWN-BRONZE-DATA parent=DOWN-BRONZE packet-mark=pm-bronze-down \
queue=pcq-down-bronze priority=6 limit-at=80M max-limit=300M
# --- Silver download children ---
add name=DOWN-SILVER-VOIP parent=DOWN-SILVER packet-mark=pm-voip-down \
queue=pcq-down-voip priority=1 limit-at=40M max-limit=150M
add name=DOWN-SILVER-DATA parent=DOWN-SILVER packet-mark=pm-silver-down \
queue=pcq-down-silver priority=4 limit-at=160M max-limit=500M
# --- Gold download children ---
add name=DOWN-GOLD-VOIP parent=DOWN-GOLD packet-mark=pm-voip-down \
queue=pcq-down-voip priority=1 limit-at=80M max-limit=200M
add name=DOWN-GOLD-DATA parent=DOWN-GOLD packet-mark=pm-gold-down \
queue=pcq-down-silver priority=2 limit-at=220M max-limit=800M
# --- Bronze upload children ---
add name=UP-BRONZE-VOIP parent=UP-BRONZE packet-mark=pm-voip-up \
queue=pcq-up-voip priority=1 limit-at=5M max-limit=20M
add name=UP-BRONZE-DATA parent=UP-BRONZE packet-mark=pm-bronze-up \
queue=pcq-up-bronze priority=6 limit-at=25M max-limit=100M
# --- Silver upload children ---
add name=UP-SILVER-VOIP parent=UP-SILVER packet-mark=pm-voip-up \
queue=pcq-up-voip priority=1 limit-at=10M max-limit=50M
add name=UP-SILVER-DATA parent=UP-SILVER packet-mark=pm-silver-up \
queue=pcq-up-silver priority=4 limit-at=70M max-limit=250M
# --- Gold upload children ---
add name=UP-GOLD-VOIP parent=UP-GOLD packet-mark=pm-voip-up \
queue=pcq-up-voip priority=1 limit-at=20M max-limit=80M
add name=UP-GOLD-DATA parent=UP-GOLD packet-mark=pm-gold-up \
queue=pcq-up-gold priority=2 limit-at=130M max-limit=400M

Why VoIP has its own limit-at: When the tier is congested, HTB guarantees the VoIP child its limit-at before serving data. Without a non-zero limit-at on a high-priority queue, HTB may still delay it in some edge cases.


Check all queues with live statistics:

/queue tree print stats

Watch a specific tier in real time:

/queue tree print stats where name~"BRONZE"

Key fields to watch:

FieldWhat it tells you
rateCurrent throughput — compare to limit-at and max-limit
droppedPackets dropped because the queue is full — indicates sustained overload
borrowsPackets forwarded above limit-at using spare parent capacity
lendsUnused guaranteed capacity lent to siblings
queued-packetsNon-zero means the tier is currently being shaped

Reset counters to start a fresh measurement window:

/queue tree reset-counters-all

The packet mark on the queue entry does not match any marked packets. Verify:

/ip firewall mangle print stats

The mangle rule byte/packet count must be increasing. If not, check:

  • Address lists contain the right customer IPs
  • The chain=forward is correct for the traffic direction
  • FastTrack is disabled for these customers

PCQ pcq-rate is 0 or too high. Set a non-zero pcq-rate on the queue type to enforce hard per-customer limits:

/queue type set pcq-down-bronze pcq-rate=10M

Priority operates among siblings (VoIP and data both under the same tier parent). If VoIP and data are under different parents, priority has no effect between them. Confirm both VoIP and data queues share the same parent= value.

Also check that the VoIP mangle mark (pm-voip-down) is incrementing — if no packets carry DSCP 46, no traffic enters the VoIP queue.

Hardware offloading bypasses software queuing. Disable it on shaped interfaces:

/interface ethernet set ether1 l2mtu=1598

Or check for FastTrack connections bypassing the queue:

/ip firewall connection print where fasttrack

  • Queue Tree — HTB fundamentals, limit-at, max-limit, priority
  • PCQ Example — Per-customer fair sharing with PCQ classifiers
  • Queue Types — PCQ, SFQ, FIFO, and other qdisc options
  • Firewall Mangle — Packet marking for queue tree classification
  • Address Lists — Managing customer tier membership