Safe-Change Script for MikroTik RouterOS
When making changes to a live network, there’s always a risk that a misconfiguration locks you out or breaks connectivity. This script gives you an automatic safety net — it snapshots your current config, then schedules an automatic rollback if you don’t cancel it in time. Think of it as a “dead man’s switch” for router changes.
What it does — overview
Before you touch anything, you run this script. It takes a full backup of your router, sets up a temporary admin account, and schedules two automatic restore jobs — one at 30 minutes and one at 60 minutes. If your changes go wrong and you lose access to the router, it will restore itself back to the working state without any intervention from you. If your changes are successful, you run the cleanup script to cancel everything and remove all traces.
Timestamp — building a unique ID
:local ts [/system clock get date]
:local tm [/system clock get time]
...
:local stamp ($yearRaw . $monthNum . $dayRaw . "-" . $timePart)
Every time the script runs it reads the current date and time from the router clock and assembles a unique timestamp string — for example 20260503-142305. This stamp is used to name every artefact the script creates, so running the script twice never overwrites a previous session. Each run is completely independent.
RouterOS returns dates in the format apr/27/2026, so the script picks apart the month, day and year separately and converts the month name to a two-digit number before assembling the final stamp.
Backup and export
/system backup save name=$backupName
/export file=$backupName
Two files are created on the router’s internal storage:
pre-change-20260503-142305.backup — a full binary snapshot of the entire router configuration including user credentials and certificates. This is the file that gets loaded if the revert fires.
pre-change-20260503-142305.rsc — a plain-text export of all configuration commands. This is a human-readable reference so you can inspect exactly what the config looked like before your changes, useful for comparing what changed or manually recovering specific settings.
Temporary admin user
/user add name=$tempUser password=$tempPass group=full comment="safe-change-temp"
The /system backup load command requires a password — specifically the password of the user who originally saved the backup. Rather than using your real admin credentials inside an automated script, a dedicated temporary user is created specifically for this purpose. The password is generated from the timestamp combined with a fragment of the router’s uptime counter, making it unique to each run. The user is tagged safe-change-temp so the cleanup script can find and remove it generically without needing to know the specific username.
Revert script
:local revertBody ""
:set revertBody ($revertBody . "/system backup load name=\"" . $backupName . "\" password=\"" . $tempPass . "\";\n")
/system script add name=$revertScriptName source=$revertBody comment="auto-revert" ...
A named script is created on the router — for example revert-20260503-142305 — containing a single command: load the backup file using the temporary user’s password. When this fires, RouterOS loads the binary backup and immediately reboots, restoring the router to the exact state it was in when the script was first run.
The revert script is stored as a named script rather than embedded directly in the scheduler. This makes it visible and auditable in the router’s script list, and means both schedulers share a single revert definition — if you need to inspect what would happen, you can read the script directly in Winbox.
Schedulers
/system scheduler add name=("auto-revert-30m-" . $stamp) start-time=$time30 ...
/system scheduler add name=("auto-revert-60m-" . $stamp) start-time=$time60 ...
Two schedulers are created, both tagged auto-revert:
T+30 minutes — the primary safety net. If you haven’t cancelled by this point the router assumes something went wrong and restores itself.
T+60 minutes — the backup safety net. This covers the scenario where you cancelled the T+30 scheduler but then something went wrong shortly after. It gives you a second chance at an automatic recovery before you’re fully on your own.
Both schedulers are set to run once (interval=0) not on a recurring basis. The calculated start times account for hour and day rollovers so the script works correctly regardless of what time of day it is run.
Cleanup script
/system script run cleanup-safe-change
The cleanup script is the “all clear” command — run it once you’ve confirmed your changes are working correctly. It removes everything the safe-change script created:
- Both auto-revert schedulers
- The named revert script
- The temporary admin user
- The
.backupand.rscfiles from router storage
Unlike the rest of the artefacts, the cleanup script itself is permanent and generic. It finds everything by tag (auto-revert on schedulers and scripts, safe-change-temp on users, pre-change- prefix on files) rather than by specific name, so it correctly cleans up across multiple safe-change sessions if the main script was run more than once.
Typical workflow
- Before making changes — run the safe-change script. Note the two revert times printed in the output.
- Make your changes — you have 30 minutes before the first automatic revert fires.
- If changes are successful — run
cleanup-safe-changebefore the T+30 time to cancel the schedulers and remove all artefacts. - If something goes wrong — do nothing. The router will restore itself at T+30 and reboot back to the pre-change state.
- If you need more time — cancel the T+30 scheduler manually with
/system scheduler remove [find comment="auto-revert"]but be aware this removes both, leaving no safety net. In that case, re-run the main script to create a fresh set.
Important notes
The revert causes a reboot. When the backup loads the router restarts, which means any active connections including your management session will drop. This is expected and intentional — it ensures a clean state.
The backup password is the temporary user’s password, not yours. Your real admin credentials are never stored anywhere in the script or the scheduler.
Each run of the script is independent. Because every artefact is uniquely named with the timestamp, running the script a second time creates a completely new set of schedulers and a new backup — the previous session’s artefacts are untouched. The cleanup script removes all outstanding sessions in one pass.
Keep an eye on the clock. The script prints the exact revert times when it runs. Make a note of them before you start making changes.
# ============================================================
# Safe-Change Script v3
# - Creates timestamped backup + export
# - Creates a temp admin user with random password
# - Creates a named revert script using that user/password
# - Schedules revert at T+30m and T+60m
# - Creates a cleanup script to remove all of the above
#
# To cancel schedulers: /system scheduler remove [find comment="auto-revert"]
# To clean up everything: /system script run cleanup-safe-change
# ============================================================
:local ts [/system clock get date]
:local tm [/system clock get time]
:local monthRaw [:pick $ts 0 3]
:local dayRaw [:pick $ts 4 6]
:local yearRaw [:pick $ts 7 11]
:local timeRaw [:tostr $tm]
:local monthNum "00"
:if ($monthRaw = "jan") do={ :set monthNum "01" }
:if ($monthRaw = "feb") do={ :set monthNum "02" }
:if ($monthRaw = "mar") do={ :set monthNum "03" }
:if ($monthRaw = "apr") do={ :set monthNum "04" }
:if ($monthRaw = "may") do={ :set monthNum "05" }
:if ($monthRaw = "jun") do={ :set monthNum "06" }
:if ($monthRaw = "jul") do={ :set monthNum "07" }
:if ($monthRaw = "aug") do={ :set monthNum "08" }
:if ($monthRaw = "sep") do={ :set monthNum "09" }
:if ($monthRaw = "oct") do={ :set monthNum "10" }
:if ($monthRaw = "nov") do={ :set monthNum "11" }
:if ($monthRaw = "dec") do={ :set monthNum "12" }
:local timePart ([:pick $timeRaw 0 2] . [:pick $timeRaw 3 5] . [:pick $timeRaw 6 8])
:local stamp ($yearRaw . $monthNum . $dayRaw . "-" . $timePart)
:local backupName ("pre-change-" . $stamp)
# ── Temp user details ─────────────────────────────────────────
# Random-ish password built from stamp + system uptime ticks
:local uptime [:tostr [/system resource get uptime]]
:local tickFrag [:pick $uptime ([:len $uptime] - 4) [:len $uptime]]
:local tempUser ("sc-" . $stamp)
:local tempPass ("Rv!" . $timePart . $tickFrag)
# ── Create backup and export ──────────────────────────────────
/system backup save name=$backupName
/export file=$backupName
:log info ("Safe-change: backup -> " . $backupName . ".backup")
:log info ("Safe-change: export -> " . $backupName . ".rsc")
# ── Create temp admin user ────────────────────────────────────
/user add name=$tempUser password=$tempPass group=full \
comment="safe-change-temp"
:log info ("Safe-change: temp user created -> " . $tempUser)
# ── Calculate T+30 and T+60 ───────────────────────────────────
:local H [:tonum [:pick $timeRaw 0 2]]
:local M [:tonum [:pick $timeRaw 3 5]]
:local H30 $H
:local M30 ($M + 30)
:if ($M30 >= 60) do={
:set M30 ($M30 - 60)
:set H30 ($H30 + 1)
}
:if ($H30 >= 24) do={ :set H30 ($H30 - 24) }
:local H60 ($H + 1)
:local M60 $M
:if ($H60 >= 24) do={ :set H60 ($H60 - 24) }
:local p30m [:tostr $M30]
:if ($M30 < 10) do={ :set p30m ("0" . [:tostr $M30]) }
:local p30h [:tostr $H30]
:if ($H30 < 10) do={ :set p30h ("0" . [:tostr $H30]) }
:local p60m [:tostr $M60]
:if ($M60 < 10) do={ :set p60m ("0" . [:tostr $M60]) }
:local p60h [:tostr $H60]
:if ($H60 < 10) do={ :set p60h ("0" . [:tostr $H60]) }
:local time30 ($p30h . ":" . $p30m . ":00")
:local time60 ($p60h . ":" . $p60m . ":00")
# ── Create named revert script ────────────────────────────────
:local revertScriptName ("revert-" . $stamp)
:local revertBody ""
:set revertBody ($revertBody . ":log warning (\"Auto-revert firing: loading " . $backupName . "\");\n")
:set revertBody ($revertBody . "/system backup load name=\"" . $backupName . "\" password=\"" . $tempPass . "\";\n")
/system script add name=$revertScriptName \
source=$revertBody \
comment="auto-revert" \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
:log info ("Safe-change: revert script created -> " . $revertScriptName)
# ── Create cleanup script ─────────────────────────────────────
# Removes the temp user, revert script, both schedulers,
# the backup and export files, and then itself.
:local cleanupBody ""
:set cleanupBody ($cleanupBody . ":log info \"Safe-change cleanup: removing schedulers\";\n")
:set cleanupBody ($cleanupBody . "/system scheduler remove [find comment=\"auto-revert\"];\n")
:set cleanupBody ($cleanupBody . ":log info \"Safe-change cleanup: removing revert script\";\n")
:set cleanupBody ($cleanupBody . "/system script remove [find name=\"" . $revertScriptName . "\"];\n")
:set cleanupBody ($cleanupBody . ":log info \"Safe-change cleanup: removing temp user\";\n")
:set cleanupBody ($cleanupBody . "/user remove [find name=\"" . $tempUser . "\"];\n")
:set cleanupBody ($cleanupBody . ":log info \"Safe-change cleanup: removing backup files\";\n")
:set cleanupBody ($cleanupBody . "/file remove [find name=\"" . $backupName . ".backup\"];\n")
:set cleanupBody ($cleanupBody . "/file remove [find name=\"" . $backupName . ".rsc\"];\n")
:set cleanupBody ($cleanupBody . ":log info \"Safe-change cleanup: removing this cleanup script\";\n")
:set cleanupBody ($cleanupBody . "/system script remove [find name=\"cleanup-safe-change\"];\n")
:set cleanupBody ($cleanupBody . ":put \"Cleanup complete. All safe-change artefacts removed.\";\n")
# Remove any previous cleanup script before creating a new one
/system script remove [find name="cleanup-safe-change"]
/system script add name="cleanup-safe-change" \
source=$cleanupBody \
comment="auto-revert" \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
:log info "Safe-change: cleanup script created -> cleanup-safe-change"
# ── Create schedulers ─────────────────────────────────────────
/system scheduler add \
name=("auto-revert-30m-" . $stamp) \
start-time=$time30 \
interval=0 \
on-event=("/system script run \"" . $revertScriptName . "\"") \
comment="auto-revert" \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
/system scheduler add \
name=("auto-revert-60m-" . $stamp) \
start-time=$time60 \
interval=0 \
on-event=("/system script run \"" . $revertScriptName . "\"") \
comment="auto-revert" \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
:log info ("Safe-change: revert at " . $time30 . " and " . $time60)
:put "========================================="
:put ("Backup file: " . $backupName . ".backup")
:put ("Export file: " . $backupName . ".rsc")
:put ("Temp user: " . $tempUser)
:put ("Revert script: " . $revertScriptName)
:put ("Revert T+30: " . $time30)
:put ("Revert T+60: " . $time60)
:put ""
:put "To cancel and clean up:"
:put " /system script run cleanup-safe-change"
:put ""
:put "To cancel schedulers only:"
:put " /system scheduler remove [find comment=\"auto-revert\"]"
:put "========================================="



