Skip to main content

MikroTik configuration backup & restore

The two MikroTik devices that run the ISC³ network — the CCR2004-1G-12S+2XS core router (192.168.88.1) and the CRS326-24G-2S+ access switch (192.168.88.254) — have their configurations backed up as plain-text RouterOS exports in this repository.

Where the backups live

Two artefacts per device, in the secretzone (tracked in git, never published on the site):

secretzone/mikrotik/ccr2004-192.168.88.1.rsc ccr2004-192.168.88.1.backup
secretzone/mikrotik/crs326-192.168.88.254.rsc crs326-192.168.88.254.backup

The .rsc is a plain-text /export — interfaces, bridge, IP addressing, DHCP server and static leases, DNS static entries, firewall, Wireguard peers, routes — with sensitive values included (since August 2026): the Wireguard server and peer private keys, plus the credentials of the three disabled leftovers — the wifx.ch L2TP uplink, the L2TP server's IPsec secret and the vpn PPP account. It is readable, diffs cleanly, and its git history is the change-by-change configuration log. The first line records the RouterOS version and export date.

The .backup is the device's own binary backup, unencrypted. It is the only artefact that carries user accounts and password hashes and certificates, but it restores only onto the same model running the same RouterOS version. Filenames are fixed, not dated — earlier ones are in git history.

Admin credentials for both devices are in the secretzone (isc3.md).

Refreshing the backups

After any configuration change on either device (see the change process), re-run the backup and commit it:

provisioning/network/mikrotik-backup.sh # both; or `… ccr` / `… crs`
git diff --stat secretzone/mikrotik
git add secretzone/mikrotik && git commit -m "MikroTik config backup"

The script needs the VPN up (NetBird, or the break-glass WireGuard peer) and expect — run it from the Mac, the WSL box has no expect and no sshpass. It exports, saves a binary backup on the device, and scps it off, reading the admin password from isc3.md at runtime (mikrotik-run.exp, mikrotik-scp.exp).

No-expect alternative (Aug 2026, for the WSL box or any machine without expect): provisioning/network/mikrotik-backup-ssh.rb produces the same artefacts over the SSH exec channel (no pty, no prompt race) using ruby net-ssh/net-scp — both installed in srv-oxidized (CT 114), so the run is: push the script there, run it per device with ROS_PASSWORD, pct pull the four files back into secretzone/mikrotik/.

A plain ssh admin@host /export > file does not work: both accounts are password-only, so there is nothing to answer the prompt outside a terminal. routeros-api.py, otherwise the reliable way to drive these devices, has no /export — it is a console command with no API equivalent. Hence the expect wrappers.

Two filters matter, and the script applies both. tr -s '\r' collapses the carriage return the pty echoes on every line; without it every line ends \r\r\n and git reports the entire file as rewritten, burying the few lines that changed. awk drops the login exchange before the export header. Keep the files CRLF — that is how the router emits them, and normalising them would produce one unreadable diff.

Still outside both artefacts: dynamic state — DHCP leases in D state, ARP, logs.

The flags are version-specific, and both devices only became RouterOS 7 on 2026-08-09

mikrotik-backup.sh passes /export show-sensitive and /system backup save dont-encrypt=yes to both. On RouterOS 6 each of those is wrong in the opposite direction: show-sensitive is not a keyword (expected end of command, and v6 emits sensitive values anyway), and dont-encrypt is rejected because omitting password already means unencrypted. macOS scp also needs -O against v6. If a device is ever rolled back to v6, that is the pair to flip.

Do not predict a MikroTik upgrade from npk size versus free space

The CRS326 has 16 MiB of flash with ~2 MiB free, and its packages are ~12 MiB. Upgrading it was called impossible on that arithmetic for most of a day; /system package update install then did it in one reboot, leaving free space unchanged — the updater stages the package instead of parking it beside the running system. A manual npk upload (WinBox drag-and-drop, scp) is the case that really does need the file to fit. Try the updater before concluding anything.

Corollary: on a v6 device, stable and long-term both answer "already up to date" forever. v7 is offered only on the testing channel — that, not the flash, is why WinBox shows no package. Landing on v7 puts the channel back to stable.

Restore process

Same device, bad configuration

Fastest path: upload the .backup and /system backup load name=... — complete, including passwords, provided the RouterOS version still matches. Otherwise, from the .rsc:

  1. Connect out-of-band if possible (serial console, or MAC-Winbox from the same L2), so the reset or a bad import cannot cut off your own session.
  2. Reset to a blank config: /system reset-configuration no-defaults=yes skip-backup=yes (the device reboots).
  3. Log in (blank admin password after reset), then upload the .rsc file: scp -O secretzone/mikrotik/ccr2004-192.168.88.1.rsc admin@<ip>:restore.rsc
  4. Import it: /import file-name=restore.rsc
  5. Recreate the user accounts, which no export carries — admin password in secretzone isc3.md. The Wireguard private keys now come with the export; wireguard.md still holds the server key as a second copy.
  6. Verify: /ip address print, /ip dhcp-server lease print, a VPN handshake from a client, and dig @192.168.88.1 rumba from the VPN.

Replacement device (hardware failure)

  • Same model: restore the binary backup (/system backup load name=...) — complete, including passwords. Otherwise follow the .rsc procedure above.
  • Different model: the .rsc is the only option; port numbers/names (sfp-sfpplus5, ether8, bonds) will need manual adjustment to the new hardware. The network page documents which machine hangs off which port.

Scripting RouterOS: find silently matches nothing

warning
set [find where dst-port=80] is a no-op, not an error

dst-port and to-ports are port-range typed, so find cannot compare them to a plain number. The expression matches zero rules, set over an empty result succeeds without output, and the configuration is unchanged — the script looks like it worked. This cost a debugging round while repointing the inbound 80/443 forwards.

Select on something find compares reliably (the comment), and always assert the match count:

# WRONG - matches nothing, changes nothing, reports nothing
/ip firewall nat set [find where chain=dstnat and dst-port=80] to-addresses=192.168.88.150

# RIGHT - select by comment, and print the count before writing
:put [:len [/ip firewall nat find where comment~"Ingoing HTTP to"]]
/ip firewall nat set [find where comment~"Ingoing HTTP to"] to-addresses=192.168.88.150

Get a rule's stable internal id with /ip firewall nat print detail (the .id=*7 field) and use that when a comment is not distinctive. Note that the printed row numbers (0, 1, …) are display indices only and shift as rules are added — never script against them.

Current state

DeviceRoleBackup filesLast refreshed
CCR2004-1G-12S+2XScore router, WAN, NAT, Wireguard hub, DNS, DHCPccr2004-192.168.88.1.rsc (818 lines) + .backup (748 KB)2026-08-09 (RouterOS 7.23.3)
CRS326-24G-2S+nodes access switchcrs326-192.168.88.254.rsc (194 lines) + .backup (60 KB)2026-08-09 (RouterOS 7.23.3)

Both refreshed on 2026-08-09, the first run to include sensitive values and binary backups; git history on the two .rsc files is the change-by-change log. The devices also still hold a few hand-made backups from 2025 (Working config.backup, Calypso nodes router-20251030-2005.backup) that were never copied off.

Re-run this after every RouterOS upgrade

A .backup restores only onto the same RouterOS version. The moment either device changes version, its binary backup stops being a restore path and only the .rsc still applies. Both were retaken on 2026-08-09 right after their upgrades — both to 7.23.3 — for that reason.