Skip to content
MikroTik RouterOS Docs

Routing Rules and Policy Routing in RouterOS

Policy routing (also called policy-based routing or PBR) allows you to make routing decisions based on criteria beyond the destination address. Instead of simply routing all traffic to 8.8.8.8 the same way, you can:

  • Route traffic from specific source addresses through different gateways
  • Send traffic arriving on certain interfaces through specific uplinks
  • Direct packets with specific routing marks to designated routing tables

RouterOS implements policy routing through three components:

  1. Routing Tables (/routing/table) - Independent sets of routes
  2. Routing Rules (/routing/rule) - Select which table to use based on packet attributes
  3. Firewall Mangle (/ip firewall mangle) - Mark packets for routing decisions

Common use cases:

  • Multi-WAN load balancing and failover
  • Source-based routing (different departments use different ISPs)
  • Traffic steering (send specific protocols through specific paths)
  • VRF-like isolation without full VRF implementation
  • RouterOS 7.x or later
  • Understanding of basic routing concepts
  • Multiple gateways or uplinks (for practical use)
┌─────────────────────────────────────────────────────────────────────┐
│ Packet Arrives │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Firewall Mangle (prerouting/output) │
│ Sets routing-mark on packet │
│ ───────────────────────────────────────────────────────────────── │
│ If marked: Packet goes directly to marked table │
│ If not marked: Continues to routing rules │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Routing Rules │
│ /routing/rule (processed in order) │
│ ───────────────────────────────────────────────────────────────── │
│ Match: src-address, dst-address, interface, routing-mark │
│ Action: lookup, lookup-only-in-table, drop, unreachable │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Selected Routing Table │
│ (main, or custom table from rule/mark) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Packet Forwarded │
└─────────────────────────────────────────────────────────────────────┘

Important: Mangle marks have higher priority than routing rules. If mangle marks a packet and that table can resolve the destination, routing rules are bypassed entirely.

MenuPurpose
/routing/tableCreate and manage routing tables
/routing/ruleDefine policy routing rules
/ip routeAdd routes to tables (IPv4)
/ipv6 routeAdd routes to tables (IPv6)
/ip firewall mangleMark packets for routing
PropertyTypeDefaultDescription
namestring-Table identifier (required)
fibflag-Create FIB (forwarding) entry for this table
commentstring-Administrative notes

Note: The main table always exists and cannot be deleted.

PropertyTypeDefaultDescription
actionenumlookupWhat to do with matching packets
dst-addressIP/prefix-Match destination address
src-addressIP/prefix-Match source address
interfacestring-Match incoming interface
routing-markstring-Match packets with this routing mark
tablestringmainRouting table for lookup actions
min-prefixinteger-Suppress routes with this prefix length or shorter
disabledyes/nonoEnable/disable rule
commentstring-Administrative notes
ActionDescription
lookupLook up in specified table; fall back to main if not found
lookup-only-in-tableLook up only in specified table; drop if not found
dropSilently discard matching packets
unreachableDiscard and send ICMP unreachable

Route traffic from different subnets through different ISPs:

# Step 1: Create routing tables
/routing/table add name=ISP1 fib
/routing/table add name=ISP2 fib
# Step 2: Add default routes to each table
/ip route add dst-address=0.0.0.0/0 gateway=192.168.1.1 routing-table=ISP1
/ip route add dst-address=0.0.0.0/0 gateway=192.168.2.1 routing-table=ISP2
# Step 3: Create routing rules for source-based routing
/routing/rule add src-address=10.0.1.0/24 action=lookup-only-in-table table=ISP1 \
comment="VLAN1 via ISP1"
/routing/rule add src-address=10.0.2.0/24 action=lookup-only-in-table table=ISP2 \
comment="VLAN2 via ISP2"
# Step 4: Verify rules
/routing/rule print

Critical: When using policy routing, you must ensure local traffic stays in the main table:

# Add these rules BEFORE ISP-specific rules (order matters!)
/routing/rule add dst-address=10.0.0.0/8 action=lookup-only-in-table table=main \
comment="Local RFC1918 - Class A"
/routing/rule add dst-address=172.16.0.0/12 action=lookup-only-in-table table=main \
comment="Local RFC1918 - Class B"
/routing/rule add dst-address=192.168.0.0/16 action=lookup-only-in-table table=main \
comment="Local RFC1918 - Class C"
# Now add ISP-specific rules
/routing/rule add src-address=10.0.1.0/24 action=lookup-only-in-table table=ISP1
/routing/rule add src-address=10.0.2.0/24 action=lookup-only-in-table table=ISP2

Route traffic based on which interface it arrives on:

# Create table
/routing/table add name=guest-table fib
# Add route
/ip route add dst-address=0.0.0.0/0 gateway=10.99.99.1 routing-table=guest-table
# Route all traffic from guest interface through guest gateway
/routing/rule add interface=ether5-guest action=lookup-only-in-table table=guest-table

Example 4: Using Firewall Mangle for Advanced Matching

Section titled “Example 4: Using Firewall Mangle for Advanced Matching”

Route HTTP traffic through a specific gateway:

# Step 1: Create routing table
/routing/table add name=http-isp fib
# Step 2: Add route to table
/ip route add dst-address=0.0.0.0/0 gateway=192.168.3.1 routing-table=http-isp
# Step 3: Mark HTTP traffic in mangle
/ip firewall mangle add chain=prerouting protocol=tcp dst-port=80,443 \
action=mark-routing new-routing-mark=http-mark passthrough=no \
comment="Mark HTTP/HTTPS for routing"
# Step 4: Create routing rule for marked traffic
/routing/rule add routing-mark=http-mark action=lookup-only-in-table table=http-isp

Note: Mangle-marked packets bypass other routing rules if the marked table resolves the destination.

Ensure responses go back through the same WAN they came in on:

# Step 1: Create tables
/routing/table add name=WAN1 fib
/routing/table add name=WAN2 fib
# Step 2: Add routes
/ip route add dst-address=0.0.0.0/0 gateway=1.1.1.1 routing-table=WAN1
/ip route add dst-address=0.0.0.0/0 gateway=2.2.2.1 routing-table=WAN2
# Step 3: Mark incoming connections
/ip firewall mangle add chain=prerouting in-interface=ether1-WAN1 \
connection-state=new action=mark-connection new-connection-mark=WAN1-conn
/ip firewall mangle add chain=prerouting in-interface=ether2-WAN2 \
connection-state=new action=mark-connection new-connection-mark=WAN2-conn
# Step 4: Mark routing based on connection mark
/ip firewall mangle add chain=prerouting connection-mark=WAN1-conn \
action=mark-routing new-routing-mark=WAN1-route passthrough=no
/ip firewall mangle add chain=prerouting connection-mark=WAN2-conn \
action=mark-routing new-routing-mark=WAN2-route passthrough=no
# Step 5: Also mark output chain for locally originated responses
/ip firewall mangle add chain=output connection-mark=WAN1-conn \
action=mark-routing new-routing-mark=WAN1-route passthrough=no
/ip firewall mangle add chain=output connection-mark=WAN2-conn \
action=mark-routing new-routing-mark=WAN2-route passthrough=no
# Step 6: Routing rules for marked traffic
/routing/rule add routing-mark=WAN1-route action=lookup-only-in-table table=WAN1
/routing/rule add routing-mark=WAN2-route action=lookup-only-in-table table=WAN2

Block routing to a specific network:

/routing/rule add dst-address=192.168.1.0/24 interface=ether4 action=drop \
comment="Block ether4 from reaching 192.168.1.0/24"

Use min-prefix to ignore default route and use only more specific routes:

# Only use routes more specific than /0 (ignore default route)
/routing/rule add src-address=10.0.5.0/24 action=lookup table=custom-table min-prefix=1

This is similar to Linux’s suppress_prefixlength option.

Routing rules also work with IPv6:

# Create IPv6 routing table
/routing/table add name=ipv6-alt fib
# Add IPv6 route
/ipv6 route add dst-address=::/0 gateway=2001:db8::1 routing-table=ipv6-alt
# Create routing rule for IPv6
/routing/rule add src-address=2001:db8:1::/48 action=lookup-only-in-table table=ipv6-alt
ActionIf route foundIf route NOT found
lookupUse found routeFall back to main table
lookup-only-in-tableUse found routeDrop packet (no fallback)

Recommendation: Use lookup-only-in-table for strict policy routing where you don’t want fallback behavior.

Rules are processed top-to-bottom. First matching rule wins.

# View current order
/routing/rule print
# Move rule to different position
/routing/rule move [find comment="Local RFC1918"] destination=0

Best practice order:

  1. Local/private subnet rules (keep local traffic in main table)
  2. Specific host or service rules
  3. Subnet/VLAN rules
  4. Default/catch-all rules

Problem 1: Local Traffic Breaks After Adding Policy Routes

Section titled “Problem 1: Local Traffic Breaks After Adding Policy Routes”

Symptom: Devices on different VLANs can’t communicate with each other.

Cause: Policy rules send local traffic through WAN tables that don’t have routes to local subnets.

Solution: Add rules for RFC1918 addresses before other rules:

/routing/rule add dst-address=10.0.0.0/8 action=lookup-only-in-table table=main
/routing/rule add dst-address=172.16.0.0/12 action=lookup-only-in-table table=main
/routing/rule add dst-address=192.168.0.0/16 action=lookup-only-in-table table=main

Symptom: Traffic matched by rule gets dropped; route exists in custom table.

Cause: Custom table has route to gateway, but gateway is not reachable from that table.

Solution: Ensure custom table can reach the gateway:

# Option 1: Add connected route to custom table
/ip route add dst-address=192.168.1.0/24 gateway=ether1 routing-table=custom-table
# Option 2: Reference main table for gateway resolution
/ip route add dst-address=0.0.0.0/0 gateway=192.168.1.1@main routing-table=custom-table

Symptom: Mangle rules exist but traffic ignores them.

Cause: Wrong chain (must use prerouting for forwarded traffic, output for local traffic).

Solution:

# For forwarded traffic
/ip firewall mangle add chain=prerouting ...
# For locally-originated traffic
/ip firewall mangle add chain=output ...

Symptom: Routing rules configured but traffic uses main table.

Causes:

  1. Mangle mark is bypassing rules
  2. Rules in wrong order
  3. Table doesn’t exist or has no routes

Solution:

# Check if mangle is marking traffic
/ip firewall mangle print stats
# Check rule order
/routing/rule print
# Verify table has routes
/ip route print where routing-table=custom-table

Symptom: Connections work sometimes; responses go out wrong interface.

Cause: No connection tracking to ensure responses use same path.

Solution: Use connection marks (see Example 5 above).

# View all routing tables
/routing/table print
# View all routing rules
/routing/rule print
# View rules with details
/routing/rule print detail
# Check routes in specific table
/ip route print where routing-table=ISP1
# View mangle rules and hit counts
/ip firewall mangle print stats where action=mark-routing
# Test which table a packet would use
# (Use /tool/packet-sniffer or torch to trace)
/tool/torch interface=ether1 src-address=10.0.1.100
# Check active connections and their marks
/ip firewall connection print where connection-mark!=""
  • Maximum 4,096 routing tables
  • Routing rules only match: src-address, dst-address, interface, routing-mark
  • For more complex matching (ports, protocols), use firewall mangle
  • IPv4 and IPv6 rules are processed together (both in /routing/rule)

In RouterOS 6.x, routing rules were under /ip route rule. Key differences:

Aspectv6v7
Menu/ip route rule/routing/rule
Table creationAutomaticMust create in /routing/table first
IPv6 rules/ipv6 route ruleUnified in /routing/rule
VRFSeparate featureIntegrated with routing tables

Migration example:

# v6 syntax
/ip route rule add src-address=10.0.1.0/24 table=isp1
# v7 syntax
/routing/table add name=isp1 fib
/routing/rule add src-address=10.0.1.0/24 action=lookup-only-in-table table=isp1
  • Static Routes (/ip route) - Define routes within tables
  • Firewall Mangle (/ip firewall mangle) - Mark packets for routing
  • VRF - Full virtual routing and forwarding isolation
  • ECMP - Load balance across multiple gateways
  • Recursive Routing - Gateway resolution through other routes
  • Netwatch - Trigger scripts on gateway failure for failover

Routing rules enable policy-based routing by selecting which routing table processes a packet:

  1. Create routing tables in /routing/table with fib flag
  2. Add routes to custom tables via /ip route ... routing-table=
  3. Create rules in /routing/rule to match traffic
  4. Preserve local routing by adding RFC1918 rules first
  5. Use mangle for advanced matching (ports, protocols)

Key points:

  • Rules process top-to-bottom; first match wins
  • Mangle marks have priority over rules
  • lookup-only-in-table prevents fallback to main table
  • Custom tables must be able to resolve their gateways
  • Always test connectivity between local subnets after changes
  • Netwatch - trigger scripts on gateway failure for failover
  • VRRP - router redundancy