Skip to content

Policy-Based Routing

Policy-based routing (PBR) selects the forwarding path based on packet attributes beyond the destination address. RouterOS supports routing decisions based on source address, incoming interface, firewall marks, protocol, and port numbers, enabling flexible traffic engineering for multi-WAN environments, VPN steering, and ISP load balancing. Rather than following a single routing table for all traffic, PBR directs different traffic flows through different gateways or network paths according to administrator-defined policy.

RouterOS 7 implements PBR through two complementary mechanisms: firewall mangle rules that mark packets with routing table references, and /ip/route/rule entries that match packet attributes and select routing tables. Understanding how these mechanisms interact and their relative priority is essential for building predictable PBR configurations that behave correctly under all conditions.

RouterOS maintains a main routing table that receives all static routes, connected routes, and protocol-learned routes by default. Policy-based routing requires creating additional routing tables to hold alternative forwarding paths. Custom routing tables isolate different sets of routes so that marked traffic consults only the routes in its designated table rather than the global routing table.

In RouterOS 7, custom routing tables must be explicitly created before any routes or marks can reference them. The fib flag installs routes from the table into the kernel forwarding information base, which is required for actual packet forwarding.

# Create routing tables for dual-ISP setup
/routing/table
add name=to-isp1 fib
add name=to-isp2 fib
# Verify tables were created
/routing/table/print

Tables without the fib flag exist only for routing policy evaluation and cannot forward traffic. Always include fib unless the table is used solely for route filtering or policy evaluation purposes.

Routes are assigned to specific tables using the routing-table parameter. Each custom table typically receives at least a default route pointing to its designated gateway, along with any additional static routes required for that traffic path.

# Default routes for each ISP in their respective tables
/ip/route
add dst-address=0.0.0.0/0 gateway=192.0.2.1 routing-table=to-isp1 check-gateway=ping
add dst-address=0.0.0.0/0 gateway=198.51.100.1 routing-table=to-isp2 check-gateway=ping
# Main table still requires its own default route
add dst-address=0.0.0.0/0 gateway=192.0.2.1 routing-table=main check-gateway=ping

The main routing table must contain routes sufficient to resolve next-hop addresses used by custom tables. RouterOS uses the main table to resolve gateway reachability even when forwarding marked traffic through a custom table, so a broken or missing main table default route can prevent custom-table traffic from forwarding correctly.

RouterOS evaluates routing tables in the following order for each packet:

  1. Mangle mark-routing check: if the packet carries a routing mark, the corresponding table is consulted immediately
  2. Routing rules: /ip/route/rule entries are evaluated in order by rule priority
  3. Main table fallback: unmarked traffic with no matching rule uses the main routing table

Mangle takes precedence over routing rules. A packet marked by mangle will not be evaluated against routing rules, making the two mechanisms mutually exclusive for any individual packet. Design configurations using one mechanism per traffic flow to avoid unexpected interactions.

Routing rules provide stateless PBR without requiring firewall processing. Each rule matches packet attributes and specifies the routing table to consult. Rules are evaluated in ascending priority order, with the first matching rule determining the lookup table.

# Route traffic from subnet 10.10.10.0/24 through ISP1
/ip/route/rule
add src-address=10.10.10.0/24 action=lookup table=to-isp1 priority=10
# Route traffic from subnet 10.20.20.0/24 through ISP2
add src-address=10.20.20.0/24 action=lookup table=to-isp2 priority=20
# All other traffic uses the main table (implicit default)

Rules support matching on source address, destination address, incoming interface, routing mark, and IP TOS/DSCP fields. The action parameter determines what happens when a rule matches: lookup searches the specified table, lookup-only-in-table restricts the lookup to that table without fallback to main, and drop discards matching packets.

Lower priority numbers are evaluated first. Rules without an explicit priority value receive a default that places them after manually prioritized rules. When multiple rules could match a packet, only the first match takes effect.

# More specific rule evaluated first (lower priority number)
/ip/route/rule
add src-address=10.10.10.100/32 action=lookup table=to-isp2 priority=5
add src-address=10.10.10.0/24 action=lookup table=to-isp1 priority=10

In this example, the specific host 10.10.10.100 routes through ISP2 while the rest of the subnet routes through ISP1.

Firewall mangle rules provide stateful PBR that tracks connections and applies consistent routing throughout the life of each flow. Mangle operates in the prerouting chain for transit traffic and the output chain for router-originated traffic, running before routing decisions are made.

The mark-routing action attaches a routing table name to a packet, directing it to that table during the forwarding lookup. Unlike packet marks that persist to other subsystems, routing marks are consumed by the routing engine and are not visible to other firewall chains.

# Mark traffic from specific source for ISP2
/ip/firewall/mangle
add chain=prerouting src-address=192.168.10.50 \
action=mark-routing new-routing-mark=to-isp2 passthrough=no

Setting passthrough=no stops processing in the mangle chain after the mark is applied. Use passthrough=yes when subsequent mangle rules need to process the same packet for other purposes such as QoS marking.

Stateful marking ensures all packets in a connection use the same path, preventing asymmetric routing that breaks TCP sessions. The pattern uses mark-connection to tag the first packet of a new connection, then mark-routing to mark all subsequent packets in that connection.

/ip/firewall/mangle
# Step 1: Mark new connections from the source subnet
add chain=prerouting src-address=192.168.10.0/24 \
connection-state=new action=mark-connection \
new-connection-mark=isp2-conn passthrough=yes
# Step 2: Apply routing mark to all packets in marked connections
add chain=prerouting connection-mark=isp2-conn \
action=mark-routing new-routing-mark=to-isp2 passthrough=no

This two-step pattern ensures that reply packets returning through the router are also marked, maintaining symmetric routing for the entire connection lifetime.

Traffic generated by the router itself, such as DNS queries, NTP synchronization, and management connections, passes through the output chain rather than prerouting. Mark router-originated traffic separately when it needs to follow specific ISP paths.

/ip/firewall/mangle
# Mark router DNS queries for ISP1 exit
add chain=output protocol=udp dst-port=53 \
action=mark-routing new-routing-mark=to-isp1 passthrough=no
# Mark all other router-originated traffic for ISP1
add chain=output action=mark-routing \
new-routing-mark=to-isp1 passthrough=no

Omitting output chain rules causes router-originated traffic to use the main routing table regardless of PBR policy, which is often the desired behavior but can cause issues when the main table’s default route is not the intended exit path.

Multi-WAN failover maintains internet connectivity when a primary ISP link fails by automatically switching to a backup link. RouterOS implements failover through route distance values combined with gateway health monitoring.

Routes with lower distance values are preferred over higher-distance routes to the same destination. Installing a primary default route with distance 1 and a backup default route with distance 2 creates automatic failover behavior: the backup route becomes active only when the primary route is removed from the routing table.

/ip/route
# Primary ISP — preferred due to lower distance
add dst-address=0.0.0.0/0 gateway=192.0.2.1 distance=1 check-gateway=ping
# Backup ISP — activates when primary fails
add dst-address=0.0.0.0/0 gateway=198.51.100.1 distance=2 check-gateway=ping

The check-gateway parameter enables RouterOS to detect gateway failures and remove the associated routes from the active routing table. RouterOS supports two monitoring modes:

ModeBehavior
pingSends ICMP echo requests to the gateway address; marks gateway down after consecutive failures
arpSends ARP requests to resolve the gateway MAC; marks down when ARP responses stop
/ip/route
add dst-address=0.0.0.0/0 gateway=192.0.2.1 distance=1 check-gateway=ping
add dst-address=0.0.0.0/0 gateway=198.51.100.1 distance=2 check-gateway=ping

When the primary gateway stops responding to pings, RouterOS marks it as unreachable and removes the primary default route, causing traffic to use the backup route automatically. The primary route is restored once the gateway resumes responding.

Direct gateway pinging only detects link-layer failures. When an ISP gateway remains reachable but upstream internet connectivity fails, gateway ping continues to succeed and the primary route stays active despite the broken internet path. Recursive routing with probe targets addresses this limitation.

# Probe targets — specific hosts reachable only through each ISP
/ip/route
add dst-address=1.1.1.1/32 gateway=192.0.2.1 scope=10
add dst-address=8.8.8.8/32 gateway=198.51.100.1 scope=10
# Default routes resolve through probe targets instead of gateways directly
add dst-address=0.0.0.0/0 gateway=1.1.1.1 distance=1 \
check-gateway=ping scope=30 target-scope=11
add dst-address=0.0.0.0/0 gateway=8.8.8.8 distance=2 \
check-gateway=ping scope=30 target-scope=11

In this pattern, the default route’s gateway is a public IP address reachable through the ISP. RouterOS pings that public IP; if the ISP’s upstream path is broken, pings fail and the route is removed even though the ISP gateway itself remains reachable. The scope and target-scope values control recursive resolution depth.

Multi-WAN PBR configurations require check-gateway on routes in each custom table to enable per-table failover behavior.

/routing/table
add name=to-isp1 fib
add name=to-isp2 fib
/ip/route
# ISP1 table routes
add dst-address=0.0.0.0/0 gateway=192.0.2.1 routing-table=to-isp1 check-gateway=ping distance=1
add dst-address=0.0.0.0/0 gateway=198.51.100.1 routing-table=to-isp1 check-gateway=ping distance=2
# ISP2 table routes
add dst-address=0.0.0.0/0 gateway=198.51.100.1 routing-table=to-isp2 check-gateway=ping distance=1
add dst-address=0.0.0.0/0 gateway=192.0.2.1 routing-table=to-isp2 check-gateway=ping distance=2

This configuration allows traffic marked for each ISP to fall back to the other ISP if its primary gateway fails, providing both PBR control and redundancy.

Equal-cost multi-path routing distributes traffic across multiple gateways simultaneously. RouterOS selects among equal-cost routes based on a hash of packet fields, distributing flows across available paths.

Install multiple default routes with identical distance values to enable ECMP:

/ip/route
add dst-address=0.0.0.0/0 gateway=192.0.2.1 distance=1 check-gateway=ping
add dst-address=0.0.0.0/0 gateway=198.51.100.1 distance=1 check-gateway=ping

RouterOS distributes new connections across both gateways. When one gateway fails its health check, RouterOS removes that route and remaining traffic shifts entirely to the surviving gateway until the failed gateway recovers.

ECMP in RouterOS uses a flow-based hash that assigns each connection to a single gateway for its duration, ensuring individual TCP sessions remain on one path. This per-flow behavior prevents packet reordering that would degrade TCP performance if packets within a connection used different paths.

The hash algorithm considers source address, destination address, and protocol by default. All connections from the same source to the same destination will use the same gateway, which can create uneven distribution when a small number of clients generate the majority of traffic.

Per-Connection Classifier provides deterministic traffic distribution using firewall mangle rules. PCC divides connections into groups based on configurable packet fields and assigns each group to a specific routing table.

/routing/table
add name=to-isp1 fib
add name=to-isp2 fib
/ip/route
add dst-address=0.0.0.0/0 gateway=192.0.2.1 routing-table=to-isp1 check-gateway=ping
add dst-address=0.0.0.0/0 gateway=198.51.100.1 routing-table=to-isp2 check-gateway=ping
add dst-address=0.0.0.0/0 gateway=192.0.2.1 routing-table=main check-gateway=ping
/ip/firewall/mangle
# Connections where (src+dst hash) % 2 == 0 go to ISP1
add chain=prerouting in-interface=ether3 connection-state=new \
per-connection-classifier=both-addresses:2/0 \
action=mark-connection new-connection-mark=isp1-conn passthrough=yes
add chain=prerouting connection-mark=isp1-conn \
action=mark-routing new-routing-mark=to-isp1 passthrough=no
# Connections where (src+dst hash) % 2 == 1 go to ISP2
add chain=prerouting in-interface=ether3 connection-state=new \
per-connection-classifier=both-addresses:2/1 \
action=mark-connection new-connection-mark=isp2-conn passthrough=yes
add chain=prerouting connection-mark=isp2-conn \
action=mark-routing new-routing-mark=to-isp2 passthrough=no

PCC ensures deterministic assignment that survives table lookups and connection tracking, providing more even distribution than pure ECMP when traffic profiles vary.

Route specific hosts or subnets through designated ISPs regardless of destination:

/routing/table
add name=to-isp1 fib
add name=to-isp2 fib
/ip/route
add dst-address=0.0.0.0/0 gateway=192.0.2.1 routing-table=to-isp1 check-gateway=ping
add dst-address=0.0.0.0/0 gateway=198.51.100.1 routing-table=to-isp2 check-gateway=ping
add dst-address=0.0.0.0/0 gateway=192.0.2.1 routing-table=main check-gateway=ping
/ip/firewall/mangle
# VLAN 10 hosts use ISP1
add chain=prerouting src-address=192.168.10.0/24 \
action=mark-routing new-routing-mark=to-isp1 passthrough=no
# VLAN 20 hosts use ISP2
add chain=prerouting src-address=192.168.20.0/24 \
action=mark-routing new-routing-mark=to-isp2 passthrough=no

NAT masquerade rules must be configured per-interface to ensure outbound traffic uses the correct source IP for each ISP:

/ip/firewall/nat
add chain=srcnat out-interface=ether1 action=masquerade # ISP1 interface
add chain=srcnat out-interface=ether2 action=masquerade # ISP2 interface

Direct specific application traffic through a designated ISP based on destination port:

/ip/firewall/mangle
# Route HTTPS traffic through ISP2 (higher-bandwidth link)
add chain=prerouting protocol=tcp dst-port=443 connection-state=new \
action=mark-connection new-connection-mark=https-conn passthrough=yes
add chain=prerouting connection-mark=https-conn \
action=mark-routing new-routing-mark=to-isp2 passthrough=no
# Route bulk FTP/SFTP through ISP1
add chain=prerouting protocol=tcp dst-port=20-21,22 connection-state=new \
action=mark-connection new-connection-mark=bulk-conn passthrough=yes
add chain=prerouting connection-mark=bulk-conn \
action=mark-routing new-routing-mark=to-isp1 passthrough=no

Route specific destination networks through a VPN tunnel while sending all other traffic through the ISP:

/routing/table
add name=to-vpn fib
/ip/route
# Corporate network routes through VPN
add dst-address=10.0.0.0/8 gateway=<vpn-tunnel-interface> routing-table=main
add dst-address=172.16.0.0/12 gateway=<vpn-tunnel-interface> routing-table=main
# Default traffic through ISP (already in main table)
/ip/firewall/mangle
# Force specific hosts to use VPN for all destinations
add chain=prerouting src-address=192.168.1.50 \
action=mark-routing new-routing-mark=to-vpn passthrough=no
/ip/route
add dst-address=0.0.0.0/0 gateway=<vpn-tunnel-interface> routing-table=to-vpn

WireGuard tunnels route traffic to destinations listed in each peer’s allowed-address parameter automatically, without requiring additional mangle rules for standard split-routing scenarios:

/interface/wireguard/peers
add interface=wg1 public-key="PEER_PUBLIC_KEY" \
endpoint-address=vpn.example.com endpoint-port=51820 \
allowed-address=10.0.0.0/8,172.16.0.0/12
# Only these prefixes are routed through the WireGuard peer

Use /ip/route/rule for stateless PBR without firewall processing overhead:

/routing/table
add name=to-isp1 fib
add name=to-isp2 fib
/ip/route
add dst-address=0.0.0.0/0 gateway=192.0.2.1 routing-table=to-isp1
add dst-address=0.0.0.0/0 gateway=198.51.100.1 routing-table=to-isp2
/ip/route/rule
add src-address=192.168.10.0/24 table=to-isp1 priority=10
add src-address=192.168.20.0/24 table=to-isp2 priority=20

Routing rules do not track connections, so reply packets may not match the same rule as the initial packet. This can cause asymmetric routing for scenarios where the router also performs NAT. Use mangle-based marking for NAT environments to ensure connection symmetry.

# View routes in a specific routing table
/ip/route/print where routing-table=to-isp1
# View all routing rules
/ip/route/rule/print
# Check gateway health status
/ip/route/print where active=yes

Use the RouterOS packet sniffer or firewall logging to verify marks are being applied:

# Add temporary logging to mangle rules for debugging
/ip/firewall/mangle
add chain=prerouting src-address=192.168.10.0/24 \
action=log log-prefix="PBR-mark:" passthrough=yes

Asymmetric routing occurs when forward and return paths use different gateways. Symptoms include broken TCP connections, NAT failures, and intermittent connectivity. Check for asymmetry by:

  1. Verifying mangle connection marks cover both new and established connections
  2. Confirming that mark-connection rules use passthrough=yes before mark-routing rules
  3. Checking that routing rules apply to both directions if used without connection tracking

Policy-based routing must be configured before NAT translation occurs in the packet processing pipeline. Mangle prerouting runs before NAT dstnat, ensuring marks are applied to the original (pre-DNAT) addresses. Source NAT in the srcnat chain runs after routing decisions, so masquerade rules correctly use the interface determined by PBR.

When troubleshooting NAT with PBR, verify that masquerade rules cover all WAN interfaces used by PBR tables. Traffic marked for a secondary ISP that exits through the secondary WAN interface requires a masquerade rule on that interface.