Skip to content

Safe Mode

Daniel Frantík edited this page Jun 15, 2026 · 2 revisions

Safe Mode

RouterOS Safe Mode is a safety net for risky configuration changes. While safe mode is active every configuration change is recorded, and if the session that owns safe mode disconnects without committing, RouterOS automatically rolls back all those changes. This is exactly what saves you when a firewall or interface change locks you out of the router — drop the connection and the box reverts itself.

tik4net exposes the four RouterOS safe-mode verbs on ITikConnection:

void SafeModeTake();      // /safe-mode/take    — enter safe mode, bound to THIS connection
void SafeModeRelease();   // /safe-mode/release — keep the changes and leave safe mode (commit)
void SafeModeUnroll();    // /safe-mode/unroll  — discard the changes now, stay connected (rollback)
bool SafeModeGet();       // is safe mode currently held by this connection? (client-side tracked)

The most important behaviour needs no method call at all: if you take safe mode and the connection drops before SafeModeRelease(), RouterOS rolls everything back on its own. SafeModeUnroll() is the explicit, on-demand version of that rollback (without disconnecting).

Transport support

Safe mode only makes sense on a persistent, session-oriented channel, because the whole mechanism hinges on "the session that took safe mode is still alive".

Transport Safe mode Mechanism
Api / ApiSsl ✅ take / release / unroll scriptable `/safe-mode/take
Telnet ✅ take / release / unroll terminal control keys: Ctrl+X takes, a second Ctrl+X commits, Ctrl+D unrolls — works on any RouterOS
Ssh ✅ take / release / unroll Ctrl+X takes/commits over the SSH shell; unroll uses the scriptable /safe-mode/unroll (RouterOS 7.18+) because Ctrl+D is the SSH EOF convention and would close the channel. Older RouterOS: unroll falls back to a disconnect-rollback
MacTelnet ✅ take / release / unroll same control keys, over the MAC-layer terminal
WinboxCli / WinboxCliMac ✅ take / release / unroll same control keys, over the encrypted WinBox terminal
WinboxNative / WinboxNativeMac ✅ take / release native M2 toggleSafeMode commands (handler [17], take 0x80003 / release 0x80005). No in-place unroll — to roll back, drop the connection without releasing. RouterOS 7.18+
Rest / RestSsl stateless — verified: /safe-mode/take returns OK but the change is never rolled back after the HTTP request ends, so there is no protection. Throws NotSupportedException
if (connection.Supports(TikConnectionCapability.SafeMode))
{
    // SafeModeTake/Release/Unroll/Get are available
}

On the CLI transports RouterOS changes the shell prompt to [admin@MikroTik] <SAFE> > while safe mode is held; tik4net recognises that prompt transparently, so all your normal commands keep working between SafeModeTake() and SafeModeRelease().

Why Ctrl+X on the CLI instead of /safe-mode/take? The control keys work on every RouterOS version, including those older than 7.18 where the scriptable /safe-mode commands do not exist. The binary-API and native-WinBox paths use the scriptable mechanism because they have no terminal to send keystrokes to.

Example: commit (keep the changes)

Make a batch of changes under the safety net, then commit so they become permanent:

using (ITikConnection connection = ConnectionFactory.CreateConnection(TikConnectionType.Api))
{
    connection.Open(HOST, USER, PASS);

    connection.SafeModeTake();   // ← from here, every change is reversible
    try
    {
        // ... risky changes — e.g. tighten the firewall ...
        connection.CreateCommandAndParameters("/ip/firewall/filter/add",
            "chain", "input",
            "action", "drop",
            "src-address", "10.0.0.0/8").ExecuteNonQuery();

        // still reachable? good — make it permanent
        connection.SafeModeRelease();   // ← changes are now kept; a later disconnect won't undo them
    }
    catch
    {
        // Do NOT release. SafeModeUnroll() discards immediately, or just let the using-block
        // close the connection — either way RouterOS rolls everything back.
        connection.SafeModeUnroll();
        throw;
    }
}

Example: automatic rollback (lockout protection)

If anything goes wrong — an exception, a lost link, the process dies — simply never call SafeModeRelease(). When the connection drops, RouterOS reverts every change made since SafeModeTake():

string ruleId;
using (ITikConnection connection = ConnectionFactory.CreateConnection(TikConnectionType.Api))
{
    connection.Open(HOST, USER, PASS);

    connection.SafeModeTake();
    ruleId = connection.CreateCommandAndParameters("/ip/firewall/filter/add",
        "chain", "input",
        "action", "drop",
        "src-address", "0.0.0.0/0")     // oops — this would lock everyone out
        .ExecuteScalar();

    // We deliberately do NOT release and just leave the using-block:
    // Dispose() closes the connection, the safe-mode owner is gone,
    // and RouterOS rolls the firewall rule back on its own.
}

// Reconnect: the rule added above is gone — RouterOS rolled it back.
using (ITikConnection check = ConnectionFactory.CreateConnection(TikConnectionType.Api))
{
    check.Open(HOST, USER, PASS);
    var found = check.CreateCommandAndParameters("/ip/firewall/filter/print", TikSpecialProperties.Id, ruleId)
                     .ExecuteList();
    // found is empty — the change was rolled back
}

Example: explicit rollback without disconnecting

SafeModeUnroll() reverts the changes immediately and keeps the connection usable — handy to abandon a failed attempt and retry on the same session (not available on native WinBox):

connection.SafeModeTake();
try
{
    ApplyRiskyConfig(connection);
    if (!StillReachable(connection))
        connection.SafeModeUnroll();   // back to the pre-take state, connection stays open
    else
        connection.SafeModeRelease();
}
finally
{
    if (connection.SafeModeGet())      // still holding it? (e.g. an exception slipped through)
        connection.SafeModeUnroll();
}

How RouterOS safe mode behaves (and its gotchas)

Understanding the router-side model explains the edge cases you will meet:

  • Owned by one session at a time. Safe mode has a single owner (/safe-mode/print shows owner = api/console/winbox, the user, and enabled/current). A second SafeModeTake() from anyone else is refused — tik4net surfaces it as a TikCommandException ("Safe mode is already held by another session").
  • Rollback is triggered by the owner session ending. On a clean Close() this is near-instant; on a hard network loss RouterOS waits for its connection-tracking timeout (a few minutes) before reverting.
  • The lock can linger after a drop — the key gotcha. When the owning session disconnects without releasing, RouterOS rolls the changes back but may keep the lock itself held by that now-dead session until the timeout expires. A new connection then sees enabled=true and gets "Safe Mode is taken by current user in another session" when it tries to take it. This is normal RouterOS behaviour, not a tik4net bug — it is the same lock you would see in WinBox/console after an unclean exit.
  • ~100-action history limit. Safe mode tracks at most ~100 actions; exceeding the limit ends safe mode and keeps the changes (no rollback). Keep safe-mode batches small.

Workaround — clearing a stuck safe-mode lock

A release from any fresh session of the same user clears a stale lock (it commits the dead session's already-rolled-back — i.e. empty — change set and drops the enabled flag). First inspect, then clear:

// Inspect — is a lock held, and by whom?
using (var conn = ConnectionFactory.OpenConnection(TikConnectionType.Api, HOST, USER, PASS))
{
    var st = conn.CreateCommand("/safe-mode/print").ExecuteSingleRow();
    // st["enabled"], st["owner"], st["user"], st["current"]

    // Clear it: a release from a fresh session releases the stale hold. Harmless if nothing is held.
    conn.CreateCommand("/safe-mode/release").ExecuteNonQuery();
}

Equivalent from a terminal / WinBox console: open a new session and press Ctrl+X twice (take, then release), or run /safe-mode/release. If release is refused because the lock is owned elsewhere, take it over first with /safe-mode/take (=on-error=unroll to discard whatever the dead session staged) and then release:

conn.CreateCommandAndParameters("/safe-mode/take", "on-error", "unroll").ExecuteNonQuery();
conn.CreateCommand("/safe-mode/release").ExecuteNonQuery();

tik4net's own test suite clears any stale lock this way (a /safe-mode/release from a throwaway API connection) before each safe-mode test — see SafeModeTest.OnInitialize.

Notes

  • SafeModeGet() reflects this connection's state, tracked client-side (true after take, false after release/unroll). It does not query the router; use /safe-mode/print for the router-side view.
  • SafeModeTake/Release/Unroll throw NotSupportedException on transports that cannot bind safe mode to a connection (Rest), and SafeModeUnroll additionally throws on WinboxNative (no in-place unroll there).

Links

Clone this wiki locally