Skip to content

Dual WAN Active-Passive Failover — hAP ax lite + Irancell

Build runbook for a MikroTik hAP ax lite: TCI fiber as primary, Irancell TD-LTE as backup, both uplinks wired, with automatic failover and automatic failback.

Adapted from active-passive-ha.md, which documents the same failover design on a hAP ax³. Read that one for the reasoning behind each mechanism — this file is the executable sequence, with the hardware differences worked in.

Port and radio plan

Interface Role
ether1 WAN1 — TCI fiber (primary, distance 1)
ether2 WAN2 — Irancell TD-LTE (backup, distance 2)
ether3, ether4 LAN — bridge ports
wifi1 LAN — client AP, 2.4 GHz, dedicated

Both uplinks on cable means the single 2.4 GHz radio serves clients only. No station mode, no virtual AP, no shared channel, no halved throughput — the radio picks its own channel and runs at full duty.

Only two wired LAN ports remain. The hAP ax lite has four ports total and two are now uplinks. If more than two wired devices are needed, put a cheap unmanaged switch on ether4.

Differences from the reference build

Reference build (ax³) This build (ax lite)
Device hAP ax³ hAP ax lite (L41G-2axD)
Radios 2 (2.4 + 5 GHz) 1 — 2.4 GHz only
Ethernet ports 5 4
WAN2 attachment WiFi station (2.4 GHz) ether2, wired
Client AP 5 GHz, dedicated radio 2.4 GHz, dedicated radio
WAN2 uplink ZiTel, 192.168.0.1 Irancell — verify, never assume
LAN subnet 192.168.8.0/24 192.168.20.0/24
WAN2 naming *-ZiTel *-Irancell
Connection-flush match plain substring escaped-dot regex (Step 8)

Wiring WAN2 also removes a whole class of failure the reference build had to design around: no SSID to read, no signal level to worry about, no wireless flapping feeding false failovers. Link state on ether2 is unambiguous.

Why 192.168.20.0/24 and not 192.168.8.0/24

Irancell distributes many Huawei LTE CPEs (B310 / B315 / B525 / B535) whose default LAN is 192.168.8.1 — identical to the reference build's LAN. Reusing it would overlap the modem's own subnet and break routing in a way no failover config can repair.

Other Irancell units are ZTE on 192.168.1.1, which collides with nothing but does break the connection-flush pattern if written as a plain substring.

192.168.20.0/24 avoids both. If you choose differently, adjust every command below.

Placeholders

Fill these in from Step 0 before running anything.

Placeholder Meaning
<TCI-GW> TCI fiber modem gateway (typically 192.168.100.1)
<IRANCELL-GW> Irancell modem gateway (192.168.1.1 or 192.168.8.1)
<AP-SSID> / <AP-PASS> client WiFi name and passphrase

Step 0 — Discovery

Run before changing anything.

/system/resource/print
/system/package/print
/interface/ethernet/print
/interface/wifi/print

Record and confirm:

  • Board name — expect hAP ax lite
  • RouterOS version — see the upgrade note below
  • Package — must show wifi-qcom, meaning the /interface/wifi menu applies. If it shows wireless instead, this document's wireless syntax is wrong and you need the legacy /interface/wireless commands.
  • Port count — expect ether1ether4. If ether5 exists, add it to the bridge in Step 2.
  • Radio count — expect a single wifi1. There is no wifi2 on this board.

Upgrade first

Every command here is written for RouterOS 7.13+. Older releases differ in ways that will bite you — notably the blackhole route flag and parts of the /interface/wifi menu. Upgrade before starting:

/system/package/update check-for-updates
/system/package/update install

Modem gateways

From a laptop plugged into each modem in turn:

ip route | grep default

Record <TCI-GW> and <IRANCELL-GW>, then confirm all three subnets differ — both modems and the planned 192.168.20.0/24. Overlapping subnets are the one failure that cannot be worked around later.


Step 1 — Clean reset

/export file=pre-dualwan
/system/reset-configuration no-defaults=yes skip-backup=no

Before running it:

  • Move the laptop to ether3 or ether4. ether1 and ether2 both become uplinks and leave the bridge.
  • Use a cable, not WiFi. The reset wipes the radio config, and with only one radio there is no second band to recover on.
  • Have WinBox ready. A no-defaults reset leaves no IP, so reconnect via Neighbors → click the MAC address, user admin, blank password. MAC-telnet and SSH will not work; this is WinBox-only.
  • Leave both modem cables unplugged until Step 4 — the firewall lands first.

Step 2 — LAN bridge, addressing, DHCP

/system/identity/set name=hAP-friend
/user/set admin password="CHANGE-ME"

/interface/bridge/add name=bridge-lan comment="LAN"

/interface/bridge/port
add bridge=bridge-lan interface=ether3
add bridge=bridge-lan interface=ether4

/ip/address/add address=192.168.20.1/24 interface=bridge-lan comment="LAN gateway"

/ip/pool/add name=pool-lan ranges=192.168.20.10-192.168.20.254
/ip/dhcp-server/add name=dhcp-lan interface=bridge-lan address-pool=pool-lan \
    lease-time=1h disabled=no
/ip/dhcp-server/network/add address=192.168.20.0/24 gateway=192.168.20.1 \
    dns-server=192.168.20.1 comment="clients resolve via router"

/ip/dns/set servers=1.1.1.1,9.9.9.9 allow-remote-requests=yes

Only ether3 and ether4ether1 and ether2 are the two uplinks. wifi1 joins the bridge in Step 7, once it's configured as an AP.

DNS forwarders must not be 8.8.8.8 or 8.8.4.4; both are pinned to specific WANs in Step 6, and a forwarder on a pinned address dies whenever that WAN does. Clients get 192.168.20.1 so nothing on the LAN depends on a probe address.

Verify:

/ip/address/print
/interface/bridge/port/print
/ip/dhcp-server/print

Renew the laptop's lease, then SSH to 192.168.20.1 and drop WinBox.


Step 3 — Interface lists, NAT, firewall

All of this lands before either uplink is connected, so the router is never exposed. Both WAN members go in now, since both are known ethernet ports.

/interface/list/add name=WAN comment="uplinks"
/interface/list/add name=LAN comment="trusted"

/interface/list/member
add list=LAN interface=bridge-lan
add list=WAN interface=ether1 comment="WAN1 TCI fiber"
add list=WAN interface=ether2 comment="WAN2 Irancell TD-LTE"

/ip/firewall/nat
add chain=srcnat out-interface-list=WAN action=masquerade comment="NAT out any WAN"

/ip/firewall/filter
add chain=input action=accept connection-state=established,related,untracked \
    comment="accept established,related,untracked"
add chain=input action=drop connection-state=invalid comment="drop invalid"
add chain=input action=accept protocol=icmp comment="accept ICMP"
add chain=input action=accept dst-address=127.0.0.1 comment="accept loopback"
add chain=input action=drop in-interface-list=!LAN comment="drop everything not from LAN"

add chain=forward action=accept connection-state=established,related,untracked \
    comment="accept established,related,untracked"
add chain=forward action=drop connection-state=invalid comment="drop invalid"
add chain=forward action=drop connection-state=new connection-nat-state=!dstnat \
    in-interface-list=WAN comment="drop new from WAN not dstnat"
add chain=forward action=accept in-interface-list=LAN out-interface-list=WAN \
    comment="LAN to WAN"
add chain=forward action=drop comment="drop everything else"

Everything references the lists, so one masquerade rule and one forward rule cover both uplinks.

The input catch-all matches !LAN rather than WAN, so interfaces that do not exist yet are denied by default. No output rules — RouterOS defaults that chain to accept.

Fasttrack is deliberately omitted. Fasttracked connections bypass the routing decision, which is precisely what breaks distance-based failover — traffic keeps riding the dead link until conntrack expires. The ax lite's dual-core 800 MHz CPU handles a home fiber line without it, though it has less headroom than the ax³.

Management hardening

/ip/neighbor/discovery-settings/set discover-interface-list=LAN
/tool/mac-server/set allowed-interface-list=LAN
/tool/mac-server/mac-winbox/set allowed-interface-list=LAN
/ip/service/set ssh address=192.168.20.0/24
/ip/service/set winbox address=192.168.20.0/24
/ip/service/disable telnet,ftp,api,api-ssl

/ip/dhcp-client/add interface=ether1 add-default-route=no use-peer-dns=no \
    use-peer-ntp=no disabled=no comment="WAN1 TCI fiber"

/ip/dhcp-client/add interface=ether2 add-default-route=no use-peer-dns=no \
    use-peer-ntp=no disabled=no comment="WAN2 Irancell"

add-default-route=no is required on both — netwatch cannot disable a dynamically added route, so both default routes must be static.

use-peer-dns=no keeps either modem from overwriting the DNS forwarders.

Now plug in: TCI fiber → ether1, Irancell → ether2. Then:

/ip/dhcp-client/print detail

Expect both status=bound, with gateway=<TCI-GW> and gateway=<IRANCELL-GW> respectively. Confirm the leases land in the subnets you recorded in Step 0.

If either stays searching... — this happened on the reference build:

/ip/firewall/filter/add chain=input action=accept protocol=udp dst-port=68 \
    in-interface-list=WAN place-before=4 comment="DHCP client replies"

Only add it if a lease genuinely fails; it opens a port that is otherwise closed.


Before building failover, prove both paths reach the internet. Add the pinned probe routes first — they let you test the backup while the primary carries traffic.

/ip/route/add dst-address=8.8.8.8/32 gateway=<TCI-GW> comment="probe-WAN1-TCI"
/ip/route/add dst-address=8.8.4.4/32 gateway=<IRANCELL-GW> comment="probe-WAN2-Irancell"

Test each:

/ping 8.8.8.8 count=5
/ping 8.8.4.4 count=5

Both must return replies. Expect the Irancell path to show noticeably higher latency than fiber — that's normal for TD-LTE.

Confirm each probe genuinely uses its own uplink:

/tool/traceroute 8.8.8.8 count=1
/tool/traceroute 8.8.4.4 count=1

Hop 0 must be <TCI-GW> and <IRANCELL-GW> respectively. If both show the same gateway, the pin failed and everything built on top will monitor one link twice.


Step 6 — Default routes and blackhole backstops

# default routes
/ip/route/add dst-address=0.0.0.0/0 gateway=<TCI-GW> distance=1 \
    comment="WAN1-TCI-default"
/ip/route/add dst-address=0.0.0.0/0 gateway=<IRANCELL-GW> distance=2 \
    comment="WAN2-Irancell-default"

# blackhole backstops - MANDATORY, see Traps
/ip/route/add dst-address=8.8.8.8/32 distance=250 comment="probe-WAN1-blackhole" blackhole
/ip/route/add dst-address=8.8.4.4/32 distance=250 comment="probe-WAN2-blackhole" blackhole

blackhole is a valueless flag in RouterOS 7.23. Both type=blackhole and blackhole=yes are rejected; place it last with no value.

Route comments are load-bearing — the netwatch scripts locate routes by comment string.

Verify:

/ip/route/print

Expect the distance-1 default flagged As, the distance-2 default plain s (standby), both /32 probes As, and both blackholes present without A.

If the WAN2 probe misbehaves

On the reference build the original WAN2 probe 1.0.0.1 answered 10/10 manual pings at 0% loss while netwatch scored 27 of 27 tests as failed — Cloudflare deprioritises ICMP and it did not survive netwatch's probe pattern through CGNAT. If 8.8.4.4 shows the same behaviour over Irancell in Step 9, try 9.9.9.9 and repoint both the probe and blackhole routes to match.


Step 7 — Client AP on wifi1

The radio is dedicated to clients, so this is a plain AP — no station mode, no virtual interface, and it chooses its own channel.

/interface/wifi/set wifi1 configuration.mode=ap configuration.ssid="<AP-SSID>" \
    security.authentication-types=wpa2-psk,wpa3-psk \
    security.passphrase="<AP-PASS>" \
    channel.band=2ghz-ax channel.width=20mhz disabled=no

/interface/bridge/port/add bridge=bridge-lan interface=wifi1 comment="2.4GHz AP"

Pick a clean channel. Scan the neighbourhood first:

/interface/wifi/scan wifi1 duration=10

Note which channels the strongest neighbours occupy, then pin one of the three non-overlapping options — 2412 (ch 1), 2437 (ch 6), or 2462 (ch 11):

/interface/wifi/set wifi1 channel.frequency=2437

width=20mhz is deliberate. 40 MHz on 2.4 GHz consumes most of the band and, in any typical apartment, causes more contention than the extra width recovers. On a single-radio device serving all clients, 20 MHz is the more reliable choice.

wpa2-psk,wpa3-psk is WPA2/WPA3 transition mode; drop wpa3-psk if an older device refuses to associate.

Country defaults to Latvia and governs legal channels and transmit power. Find the accepted spelling with tab completion — plain Iran is rejected:

/interface/wifi/set wifi1 configuration.country=ir<TAB>

Verify:

/interface/wifi/print
/interface/wifi/monitor wifi1 once

Expect state: running. The R flag appears once a client associates. Connect a phone and confirm it gets a 192.168.20.x address with working internet.


Step 8 — Failover scripts

The regex differs from the reference build and this matters.

Plain substring matching is unsafe here. If Irancell is 192.168.1.0/24, the pattern "192.168.1" also matches 192.168.100.46, and an unescaped . is a regex wildcard that happily matches the 0. Escaping the dots makes 192\.168\.1\. fail to match 192.168.100.46, which is the behaviour we need.

/system/script/add name=wan1-down policy=read,write,test source={
    :log warning "WAN1 TCI DOWN - failing over to Irancell";
    /ip route disable [find comment="WAN1-TCI-default"];
    /ip firewall connection remove [find where reply-dst-address~"192\\.168\\.100\\."];
}

/system/script/add name=wan1-up policy=read,write,test source={
    :log info "WAN1 TCI UP - failing back from Irancell";
    /ip route enable [find comment="WAN1-TCI-default"];
    /ip firewall connection remove [find where reply-dst-address~"192\\.168\\.1\\."];
}

/system/script/add name=wan2-down policy=read,write,test source={
    :log warning "WAN2 Irancell DOWN - no backup uplink available";
}

/system/script/add name=wan2-up policy=read,write,test source={
    :log info "WAN2 Irancell UP - backup uplink ready";
}

Adjust both patterns to the actual modem subnets. The wan1-down pattern must match the fiber subnet; the wan1-up pattern must match the Irancell subnet.

Keep the flush scoped. A blanket remove [find] wipes the entire connection table including the SSH session to the router, disconnecting you on every failover.

Test by hand before wiring netwatch

/system/script/run wan1-down
/ip/route/print

Expect WAN1-TCI-default flagged Xs and WAN2-Irancell-default promoted to As. Confirm internet still works from a client, then:

/system/script/run wan1-up

Step 9 — Netwatch

/tool/netwatch/add name="WAN1-TCI" host=8.8.8.8 type=icmp interval=10s timeout=2s \
    packet-count=10 packet-interval=500ms thr-loss-percent=100 \
    down-script="/system script run wan1-down" \
    up-script="/system script run wan1-up" \
    comment="TCI fiber health" disabled=no

/tool/netwatch/add name="WAN2-Irancell" host=8.8.4.4 type=icmp interval=10s timeout=2s \
    packet-count=10 packet-interval=500ms thr-loss-percent=100 \
    down-script="/system script run wan2-down" \
    up-script="/system script run wan2-up" \
    comment="Irancell health (log only)" disabled=no

Named scripts only. Inline down-script=":log warning \"...\"" with escaped quotes corrupted the entry on the reference build, leaving a stray test-script="" that made it report down permanently regardless of probe results.

Scripts are referenced as /system script run <name> — netwatch's script fields hold code, not references.

The WAN2-Irancell monitor only logs and changes no routes: Irancell is already the lowest-priority path with nothing to demote it to. Its value is telling you the backup has silently died before you need it.

Those thresholds are anti-flap tuning, not cosmetics. Netwatch defaults produced three false failovers in seven minutes on the reference build — and the backup monitor flapped even with all thresholds stripped and no scripts attached, proving the links themselves have ordinary transient loss. Each test now spans ~5 s across 10 packets and fails only at 100% loss. Cost: ~15–20 s detection instead of ~10 s.

Verify both entries reach up and that their since timestamps stay steady across several prints:

/tool/netwatch/print

If a since timestamp keeps resetting, the link is flapping and the thresholds need loosening further — not tightening.


Step 10 — NTP, testing, backup

NTP

/system/ntp/client/set enabled=yes servers=0.pool.ntp.org,1.pool.ntp.org
/system/clock/set time-zone-name=Asia/Tehran

Both DHCP clients run use-peer-ntp=no, so nothing else sets the clock. Without this the log timestamps drift and outage postmortems are worthless.

Test A — cable pull

Unplug the fiber from ether1. Two mechanisms fire together: the connected route disappears so the distance-1 default goes inactive on its own, and netwatch records a down. Traffic should be on Irancell within ~15 s. Plug it back and confirm failback.

The case link-state detection cannot see: cable up, lease intact, modem healthy, but no internet beyond it.

/ip/firewall/filter/add chain=output action=drop dst-address=8.8.8.8 \
    out-interface=ether1 place-before=0 comment="TEST fiber loss"

# wait 30s, check /ip/route/print, then remove
/ip/firewall/filter/remove [find comment="TEST fiber loss"]

Reference build result: down → failover → up → failback in 18 seconds.

Test C — real modem power-cycle

  1. Power off the TCI modem
  2. Confirm traffic moves to Irancell (~15 s)
  3. Power the modem back on and do nothing
  4. Failback occurs automatically

Wait 3–5 minutes before judging failback. A fiber ONT cold boot must complete link training, DHCP, and upstream PPPoE before it passes traffic. Netwatch reacts within ~15 s of the fiber genuinely working, not of it being powered on. The reference build went down at 22:37:22 and recovered automatically at 22:42:00 — concluding it was broken after 60 seconds would have been wrong.

Backup

/export file=dualwan-final
/system/backup/save name=dualwan-final
scp admin@192.168.20.1:dualwan-final.rsc ~/

Store it off the device — a reset destroys it.


Operations

/ip/route/print where dst-address=0.0.0.0/0
Flags Gateway Meaning
As <TCI-GW> TCI fiber carrying traffic (normal)
As <IRANCELL-GW> Irancell carrying traffic (failed over)
Xs <TCI-GW> fiber disabled by wan1-down — it is down
s <IRANCELL-GW> Irancell idle in standby

Only one route ever carries A.

/tool/netwatch/print

WAN1-TCI down while WAN2-Irancell is up means you are running on the backup with no further fallback.

Definitive path check

/tool/traceroute 1.1.1.1 count=1

Hop 0 is the gateway actually in use. Do not test with 8.8.8.8 or 8.8.4.4 — both are pinned and always report their own WAN regardless of which is active.

Failover history

/log/print where message~"WAN1"

Traps

Carried over from the reference build. None are recoverable from a config export.

1. Blackhole routes are mandatory

Without them, powering off a modem deactivates that WAN's pinned probe route, so netwatch's pings fall through to 0.0.0.0/0 and answer over the other WAN. Netwatch reports the dead link as up for the whole outage.

Traffic still moves to the backup by link-level failover, but no down is recorded — and with no down there is no down → up edge, so the up script never fires and the router stays on the backup permanently.

Symptom: default route stuck at Xs while /ping 8.8.8.8 succeeds and the probe route shows As.

2. Validate the probe target

A target that answers manual pings perfectly can still fail every netwatch test. See Step 6.

3. Netwatch defaults flap

Three false failovers in seven minutes. Each flap flushes the connection table and bounces traffic between uplinks — worse than the outages it protects against. See Step 9.

4. Inline netwatch scripts corrupt the entry

Always use named scripts. See Step 9.

5. Only two wired LAN ports

ether1 and ether2 are uplinks. If more than two wired devices are needed, add an unmanaged switch on ether4 — do not repurpose an uplink port.

RouterOS 7.23 syntax notes

Wrong Right
type=blackhole blackhole (valueless flag, place last)
blackhole=yes blackhole
thr-max-rtt=1s thr-max=1s
configuration.country=Iran tab-complete — long ISO-style names

Checklist

  • Step 0 — board confirmed hAP ax lite, wifi-qcom package, port count noted
  • RouterOS upgraded to 7.13+
  • All three subnets confirmed distinct
  • <IRANCELL-GW> verified (not assumed)
  • Reset performed over cable, laptop on ether3/ether4
  • LAN up on ether3–4, SSH to 192.168.20.1 working
  • Firewall and NAT in place before either uplink connected
  • TCI on ether1, Irancell on ether2, both leases bound
  • Both probes ping successfully
  • Traceroute confirms each probe uses its own gateway
  • All six routes present, both blackholes flagged
  • AP running on a clean channel (1, 6, or 11) at 20 MHz
  • Clients getting 192.168.20.x over WiFi with internet
  • Scripts use escaped-dot regex matching the real subnets
  • Scripts tested manually before netwatch
  • Both netwatch entries up and stable
  • Test A passes (cable pull)
  • Test B passes (simulated loss)
  • Test C passes (modem power-cycle, waited 3–5 min)
  • NTP synchronised
  • /export downloaded off the device