MikroTik load balancing with PCC splits new connections across two WAN links, and it keeps every packet of a connection on the same link so sessions survive. Most guides stop at the config. A lab lets you prove the two things that matter: that traffic really leaves on both WANs, and that it fails over when one dies.
This lab does both on RouterOS 7.24.5. In our run 20 connections split 10 and 10 across the WANs, failover took about 22 seconds, and recovery took about 8. The files are in our public mikrotik-labs repository.
PCC, ECMP and nth at a glance
| Method | Decides per | Keeps a session on one WAN | Setup | Best for |
|---|---|---|---|---|
| PCC | Connection, by hash | ✅ Yes | ⚠️ Mangle rules and routing tables | Dual WAN for users, stable sessions |
| ECMP | Route, several gateways | ⚠️ Depends on the hash policy | ✅ One route | Simple equal links |
| nth | Every nth connection | ✅ With connection marks | ⚠️ Mangle rules | Strict rotation |
Bottom line: PCC is the usual choice for two internet links, because connections stay put. Test it in a lab first, since a wrong mark or a missing return path breaks traffic in ways that are hard to read on a live router. NetPilot builds this lab from a prompt in about 2 minutes, and you verify it on the real RouterOS CLI.
The lab
┌─ ether2 10.1.0.2 ─ isp1 ─┐
pc1 ─ ether4 ─ gw web 203.0.113.10
└─ ether3 10.2.0.2 ─ isp2 ─┘gw balances. isp1 and isp2 are the two providers. web is reachable through both, so its logs show which WAN a connection used.
Build it with the agent
"Build a MikroTik dual WAN lab: a CHR gateway with two uplinks to two CHR ISP routers, both reaching one Linux web server, and a Linux PC on the LAN. Configure PCC load balancing with check-gateway failover on RouterOS 7, then prove traffic uses both WANs and fail one over."
The agent designs the topology, writes the RouterOS config, deploys, and runs the checks. Direct CLI is always available too: open a terminal on gw and read the mangle counters yourself.
Or run it yourself
On a Linux host with containerlab v0.82.0 or newer:
git clone https://github.com/netpilot-labs/mikrotik-labs
cd mikrotik-labs/03-dual-wan-pcc
sudo containerlab deploy -t dual-wan-pcc.clab.ymlThe config, step by step
This is the gw.rsc startup config from the lab, in order.
1. Addresses and routing tables
/ip address add address=10.1.0.2/30 interface=ether2 comment=WAN1
/ip address add address=10.2.0.2/30 interface=ether3 comment=WAN2
/ip address add address=192.168.88.1/24 interface=ether4 comment=LAN
/routing table add name=to_wan1 fib
/routing table add name=to_wan2 fibOn RouterOS 7 the routing table has to exist before a route or a mangle rule can use it. The fib flag is what puts it in the forwarding path.
2. Keep inbound connections on their WAN
/ip firewall mangle add chain=prerouting in-interface=ether2 connection-mark=no-mark action=mark-connection new-connection-mark=wan1_conn passthrough=yes
/ip firewall mangle add chain=prerouting in-interface=ether3 connection-mark=no-mark action=mark-connection new-connection-mark=wan2_conn passthrough=yesA connection that arrives on a WAN must answer on the same WAN. These two rules make sure of that.
3. Classify LAN connections
/ip firewall mangle add chain=prerouting in-interface=ether4 dst-address-type=!local connection-mark=no-mark per-connection-classifier=both-addresses-and-ports:2/0 action=mark-connection new-connection-mark=wan1_conn passthrough=yes
/ip firewall mangle add chain=prerouting in-interface=ether4 dst-address-type=!local connection-mark=no-mark per-connection-classifier=both-addresses-and-ports:2/1 action=mark-connection new-connection-mark=wan2_conn passthrough=yes2/0 and 2/1 mean two buckets. both-addresses-and-ports feeds source and destination address and port into the hash, so two connections to one server can land on different WANs. dst-address-type=!local leaves traffic for the router itself alone.
4. Turn connection marks into routing marks
/ip firewall mangle add chain=prerouting in-interface=ether4 connection-mark=wan1_conn action=mark-routing new-routing-mark=to_wan1 passthrough=no
/ip firewall mangle add chain=prerouting in-interface=ether4 connection-mark=wan2_conn action=mark-routing new-routing-mark=to_wan2 passthrough=no
/ip firewall mangle add chain=output connection-mark=wan1_conn action=mark-routing new-routing-mark=to_wan1 passthrough=no
/ip firewall mangle add chain=output connection-mark=wan2_conn action=mark-routing new-routing-mark=to_wan2 passthrough=no5. NAT and routes with failover
/ip firewall nat add chain=srcnat out-interface=ether2 action=masquerade
/ip firewall nat add chain=srcnat out-interface=ether3 action=masquerade
/ip route add dst-address=0.0.0.0/0 gateway=10.1.0.1 routing-table=to_wan1 check-gateway=ping
/ip route add dst-address=0.0.0.0/0 gateway=10.2.0.1 routing-table=to_wan1 distance=2
/ip route add dst-address=0.0.0.0/0 gateway=10.2.0.1 routing-table=to_wan2 check-gateway=ping
/ip route add dst-address=0.0.0.0/0 gateway=10.1.0.1 routing-table=to_wan2 distance=2
/ip route add dst-address=0.0.0.0/0 gateway=10.1.0.1 check-gateway=ping
/ip route add dst-address=0.0.0.0/0 gateway=10.2.0.1 distance=2Each table has its own WAN at distance 1 and the other WAN at distance 2. That second route is the failover.
Your SSH session is safe through all of this in containerlab, because management lives in its own routing table.
Prove the split
Send 20 connections from pc1:
docker exec clab-dual-wan-pcc-pc1 sh -c 'for i in $(seq 1 20); do curl -s -m3 -o /dev/null http://203.0.113.10/; done'A packet capture on web shows where each SYN arrived. In this run 10 came in on eth1 from 10.1.0.2 and 10 on eth2 from 10.2.0.2:
18:19:30.689925 eth1 In IP 10.1.0.2.38704 > 203.0.113.10.80: Flags [S]
18:19:30.694241 eth1 In IP 10.1.0.2.38706 > 203.0.113.10.80: Flags [S]
18:19:30.698581 eth2 In IP 10.2.0.2.38718 > 203.0.113.10.80: Flags [S]PCC is a hash, not a rotation, so do not expect a perfect split every time. In an earlier run of 21 connections the same rules gave 7 and 14. The mangle counters show it on the router:
[admin@gw] > /ip firewall mangle print stats
Columns: CHAIN, ACTION, BYTES, PACKETS
# CHAIN ACTION BYTES PACKETS
0 prerouting mark-connection 240 2
1 prerouting mark-connection 240 2
2 prerouting mark-connection 444 7
3 prerouting mark-connection 840 14
4 prerouting mark-routing 2 856 44
5 prerouting mark-routing 6 272 98
6 output mark-routing 0 0
7 output mark-routing 0 0Rules 2 and 3 count new connections per WAN. Rules 4 and 5 count every packet of those connections.
Prove the failover
Take WAN1 down at the provider, so gw still sees its link up. That is the failure check-gateway exists for:
/interface disable ether2Run that on isp1. About 22 seconds later the distance 1 routes for WAN1 go inactive and the backups take over:
[admin@gw] > /ip route print where dst-address=0.0.0.0/0 and routing-table!=mgmt
Flags: I - INACTIVE, A - ACTIVE; s - STATIC
Columns: DST-ADDRESS, GATEWAY, ROUTING-TABLE, DISTANCE
# DST-ADDRESS GATEWAY ROUTING-TABLE DISTANCE
0 Is 0.0.0.0/0 10.1.0.1 main 1
1 As 0.0.0.0/0 10.2.0.1 main 2
4 Is 0.0.0.0/0 10.1.0.1 to_wan1 1
5 As 0.0.0.0/0 10.2.0.1 to_wan1 2
6 As 0.0.0.0/0 10.2.0.1 to_wan2 1
7 s 0.0.0.0/0 10.1.0.1 to_wan2 2All 20 new connections from pc1 then answered with HTTP 200. Enable ether2 on isp1 again and the routes moved back after about 8 seconds. Expect those 22 seconds to hit users during a real outage, and test any change to the ping target or timers the same way.
What this lab does not measure
The CHR free license limits each interface to 1 Mbps upload, as MikroTik documents, so this lab proves behavior and not bandwidth. Connection marks, routes and failover timing all work the same at any speed the lab can show, and that is the part worth testing.
FAQ
How does PCC load balancing work on MikroTik?
Per Connection Classifier hashes fields of each new connection, such as addresses and ports, and a mangle rule marks the connection for one WAN according to the hash result. Every packet of that connection then uses the same WAN, so sessions do not break. It is a hash, not round robin, so the split is close to even over many connections and can lean to one side over a few.
What changed for PCC on RouterOS 7?
On RouterOS 7 you create the routing tables first with /routing table add name=to_wan1 fib, and a route selects its table with routing-table=to_wan1. The mangle rules keep the same shape as before.
Does load balancing add the speed of both WAN links together?
Not for a single connection. PCC keeps each connection on one WAN, so one download uses one link. The gain comes across many connections, which spread over both links.
How fast is MikroTik failover with check-gateway=ping?
In our lab the route moved to the backup about 22 seconds after the upstream interface went down, and moved back about 8 seconds after it returned. Connections marked for the failed WAN used the backup route with no change to the mangle rules.
Next steps
Start with CHR in containerlab if the setup is new to you, or practice the basics in the MTCNA lab. The MikroTik lab page covers the hosted route, and MikroTik's own load balancing documentation covers the other methods. To rehearse your own WAN change, describe it at app.netpilot.io.
NetPilot is not affiliated with MikroTik. MikroTik and RouterOS are trademarks of their owner.