Back to blog

MikroTik / Automation  ·  Tutorial  ·  July 2026

MikroTik
Managing a Fleet

One router you configure by hand. Twenty routers across twenty sites you configure by hand once, and then spend your life logging into them one at a time to change a DNS server. This is how to drive a fleet of RouterOS boxes from one place — the REST API, Ansible, and a scripted push that stays idempotent. The Cisco Ansible post, in RouterOS.

MikroTik RouterOS Automation REST API Ansible

Manual configuration doesn't scale, and it doesn't stay consistent — the whole reason a fleet drifts into twenty subtly-different snowflakes is that every change was typed by hand into whichever boxes someone remembered. Automation fixes both: one source of truth, pushed everywhere, verifiable. RouterOS 7 gives you three levels of it. The REST API is the low-level primitive — HTTP verbs against /rest/… paths, scriptable from anything. Ansible (the community.routeros collection) is the declarative layer — describe the desired state, let it converge. And on-box scripting + scheduler (from the scripting post) handles what's best done locally.

The engineering goal throughout is idempotency: running the same push twice changes nothing the second time. Get that and automation is safe to run on a schedule; miss it and you're duplicating rules across the estate. This mirrors Cisco IOS: Managing a Fleet with Ansible — same discipline, RouterOS transports.

Prerequisites

01

The REST API — HTTP Against RouterOS

RouterOS 7 exposes the entire menu tree over REST. Every path you know from the CLI is a URL; GET reads, PUT adds, PATCH edits, DELETE removes. Anything that speaks HTTP can now configure the router.

On the router — enable REST (rides on www-ssl)
> /ip service set www-ssl address=10.10.99.0/24 certificate=rest-cert disabled=no
# Use a real cert and restrict to the mgmt subnet — this is full control.
From the management host — read and change over REST
# Read: GET the address table as JSON.
$ curl -sk -u auto:secret https://10.10.0.1/rest/ip/address | jq .

# Change: PATCH the DNS server across the whole tree in one call.
$ curl -sk -u auto:secret -X POST https://10.10.0.1/rest/ip/dns/set \
    -H 'Content-Type: application/json' -d '{"servers":"1.1.1.1,9.9.9.9"}'
# Wrap in a for-loop over a host list and you've changed 20 boxes.

02

Ansible — Declarative Across the Estate

A loop over curl works but isn't idempotent and has no state model. Ansible's community.routeros collection gives you inventory, desired-state modules, and a dry-run — the grown-up version of the same idea.

playbook.yml — set NTP everywhere, idempotently
- hosts: mikrotiks
  gather_facts: no
  connection: ansible.netcommon.network_cli
  vars:
    ansible_network_os: community.routeros.routeros
  tasks:
    - name: Ensure NTP client config
      community.routeros.command:
        commands:
          - /system ntp client set enabled=yes servers=time.google.com
    - name: Read version (for a report)
      community.routeros.command:
        commands: [ /system resource print ]
      register: ver
$ ansible-playbook -i inventory playbook.yml --check   # dry run first
💡 Pro Tip — routeros_api vs routeros_command

The collection has two transports: community.routeros.command (over SSH, runs CLI lines) and community.routeros.api (over the binary API, structured). For pushing config, prefer the API modules where they exist — they understand RouterOS's .id-keyed items and can genuinely converge to a desired state, rather than blindly re-running CLI that may duplicate entries. Use command for reads and for menus the API modules don't cover yet.

03

Idempotency — The Whole Point

The failure mode of naive automation is duplication: a push that adds a firewall rule adds it again every run. The fix is find-then-set, so the second run is a no-op.

RouterOS script — add-only-if-absent pattern
# Instead of a bare add (which duplicates), check first:
> :if ([:len [/ip firewall address-list find list=MGMT address=10.10.99.0/24]] = 0) do={ \
    /ip firewall address-list add list=MGMT address=10.10.99.0/24 }
# For settings, 'set' is naturally idempotent — prefer it over add/remove:
> /ip dns set servers=1.1.1.1,9.9.9.9   # same result every run
⚠ Gotcha — Automation Amplifies Mistakes

A wrong command typed into one router is one broken router; the same command pushed to a fleet is an outage across every site at once — including the management path you were using to reach them. Always dry-run (--check), stage to one canary box, and never push a firewall input-chain change to the whole estate without a scheduled auto-revert or a second access path. The Safe Mode habit doesn't protect API pushes — that safety is yours to build in.

Takeaways

  1. Manual config doesn't scale or stay consistent — fleets drift into snowflakes because every change was typed by hand. Automation is the fix.
  2. The REST API exposes the whole menu tree over HTTP — GET/POST/PATCH/DELETE against /rest/…, scriptable from anything.
  3. Ansible's community.routeros adds inventory, desired state, and dry-run — the declarative layer over the same operations.
  4. Prefer the API modules over CLI-command modules for pushes — they converge to state instead of blindly re-running.
  5. Idempotency is the whole point: find-then-add, and favour set (naturally idempotent) over add. A second run should change nothing.
  6. Automation amplifies mistakes — dry-run, canary, and keep a second path before touching the input chain fleet-wide.

A dozen sites and every change is a dozen logins?

NOCTIS builds automation for MikroTik and Cisco fleets across Crete — one source of truth, safe staged pushes, and configuration that stays consistent across every site instead of drifting apart.

Book a Discovery Call →