Skip to content
MikroTik··14 dk okuma·İleri

Bulk MikroTik Device Management with the RouterOS API: Architecture Notes from the Field

The architecture of managing hundreds or thousands of MikroTik devices centrally and autonomously over the RouterOS API: librouteros binary API, an asyncio bridge, a Redis job queue, idempotent config push, circuit breakers, and v6/v7 differences. Lessons learned while building our own automation platform.

#mikrotik#routeros#api#automation#network#python#librouteros
TL;DR

The architecture of managing hundreds or thousands of MikroTik devices centrally and autonomously over the RouterOS API: librouteros binary API, an asyncio bridge, a Redis job queue, idempotent config push, circuit breakers, and v6/v7 differences. Lessons learned while building our own automation platform.

İçindekiler

Short answer: you can manage a few dozen devices by hand, one at a time; with hundreds or thousands of devices that becomes impossible. At scale, the right path is to build automation that runs over the RouterOS API, works with structured data, is backed by a job queue, and is idempotent. This article covers the architectural decisions and pitfalls we learned in the field while building our own centralized MikroTik management platform for exactly this need: why the binary API, why a job queue, how to push config safely, and what real edge cases in RouterOS make automation harder.

This is not a product pitch; it’s a roadmap for engineers building a similar system, and an answer to organizations weighing whether to have their MikroTik infrastructure managed externally about “what’s really involved.” If you’re new to MikroTik, we recommend starting with the fundamentals of what MikroTik is and where RouterOS fits before diving in here.

Ways to manage RouterOS programmatically: API, SSH, and REST

There are three ways to apply automation to RouterOS from the outside; at scale the choice is clear:

  • Binary API (8728 / api-ssl 8729): RouterOS’s own binary protocol. Commands and returned records are structured: you get field-value pairs from menus like interface, ip address, firewall filter, and you don’t parse text. This is the correct layer for bulk management.
  • REST API (v7, HTTP/HTTPS): Introduced with RouterOS 7, returns JSON. It’s convenient for simple integrations; we chose the binary API because we also had to support v6 devices and stay within a single client abstraction (REST exists only on v7).
  • SSH: The most flexible but most fragile path. Output is free text, changes from version to version, and is tedious to parse. We keep SSH only for the narrow jobs the API can’t cover (see the /export section below).

Our decision: the binary API as the primary method, SSH as a secondary, narrow-purpose method. On the Python side we do this with the librouteros library; a mature client that implements the protocol correctly and handles the binary API’s login flow for you.

Sending a command to hundreds of devices at once: the job-queue architecture

The most common mistake is the reflex of “if there are a thousand devices, let me open a thousand connections at once.” This quickly brings a single server and its network stack to their knees; worse, one slow device stalls the entire process. The pattern that works at scale is different:

  1. Fan-out: When a bulk operation is triggered (e.g. “back up all devices”), a separate job is generated for each device.
  2. Priority queue: These jobs are written to a Redis queue. Different job types are held at different priorities; an urgent reconcile jumps ahead of a routine monitoring sweep.
  3. Horizontal worker scaling: The queue is consumed in parallel by many independent worker processes. You increase the degree of parallelism not by speeding up one giant loop, but by increasing the number of worker replicas. This makes the system naturally horizontally scalable on Docker/Kubernetes.

This architecture has three critical guarantees:

  • Reliable queue (ACK/NACK): When a job is picked up, it’s marked as “in progress.” If a worker crashes, the job automatically returns to the queue after a certain time, so no device is silently skipped.
  • Per-device distributed lock: For each device a lock (lock:device:<id>) is acquired in Redis. This prevents two workers from writing configuration to the same MikroTik at the same time. If the lock can’t be acquired, the job is retried with a short exponential backoff (5 → 10 → 20 → 40 s).
  • Circuit breaker: Repeatedly trying to connect to a powered-off or unreachable device is a waste of both time and queue capacity. After a certain number of consecutive connection failures (3 in our case), the “circuit opens” for that device and it isn’t retried during a cooldown period (5 min). When the device comes back, the circuit closes automatically.

An honest limit: Not every bulk job is a “true fan-out.” For instance, you can also put a health-check of thousands of devices on the queue as a single job and loop through them sequentially inside the worker; that’s simple but serial. True parallelism appears when you split the work per device. Which jobs fan out and which stay serial is a deliberate design decision.

This kind of centralized management only makes sense alongside a monitoring layer; we also tie the inventory into our network monitoring stack. For multi-branch and ISP-type deployments, a dedicated ISP network management guide is complementary reading.

Bringing a synchronous library into the async world: the asyncio.to_thread bridge

librouteros is a synchronous library: when you send a command, it blocks until the response arrives. Our workers, however, are asyncio-based. Mixing the two naively means that a single slow device locks up the entire event loop, and therefore every device that worker is processing.

The solution is to move each device I/O call onto a thread:

# Run the synchronous librouteros call without blocking the event loop
api = await asyncio.to_thread(librouteros.connect, host=ip, username=user,
                              password=pw, port=8728, timeout=10)
data = await asyncio.to_thread(lambda: tuple(api.path("interface")))

This small-looking bridge is critical at scale: connect, read, command. Every step that talks to the device is inside to_thread. Otherwise the system looks “async” but in practice runs at the speed of a single device.

Note: In languages like Go, concurrent access to thousands of devices is more naturally built with goroutines. We stayed in Python because the rest of the platform (FastAPI, the data model, team familiarity) is Python, and we compensated with this bridge. Language choice isn’t a right-or-wrong; it’s a contextual decision.

The API’s sneakiest trap: the difference between “print” and “action”

In the RouterOS API, reading a menu and sending an action to it are different calls, and this is the trap that most often catches newcomers to automation.

  • Iterating over api.path("interface") runs /interface/print in the background; that is, it reads.
  • But commands like reboot, upgrade, backup save cannot be sent this way; they require a separate call that runs the command directly (such as api(cmd="/system/reboot")).

Without knowing this difference, hours get spent on “why isn’t reboot working.” In our code this distinction is marked with a comment at the top of every action function, precisely so we don’t fall into the same trap six months later.

A related fact: the connection dropping after a reboot is normal. As the device restarts, the API session naturally disconnects; this exception should not be counted as an error but swallowed (and recorded as “device rebooted”).

Why /export isn’t in the API, and being forced to fall back to SSH

The classic way to get a full, readable configuration dump of a MikroTik is the /export command. But the binary API doesn’t return /export. This was one of the most concrete walls we hit while building the automation.

There are two solutions, and we use both:

  1. SSH for a real /export: When a verbatim dump of the configuration is needed (e.g. for audit or archival), we open SSH with paramiko and grab the /export output. This is a textbook example of the “keep SSH only for narrow jobs” principle.
  2. API-based “pseudo-export”: Reading the menus section by section and producing an RSC-like text. It doesn’t require SSH but isn’t as complete as /export.

The lesson: the binary API is powerful but doesn’t cover everything; a mature automation must be able to fall through to SSH cleanly where the API ends.

Safe bulk changes: desired state → diff → idempotent command → verification

A bulk configuration change doesn’t have to be scary; what’s scary is a blind change. Our reconcile loop goes through these steps:

  1. Desired state: The device’s target state is generated from a YAML template (NTP, SNMP, firewall baseline, management users, etc.).
  2. Actual state: The current configuration is read from the device over the API.
  3. Diff: The two states are compared section by section; only the difference (drift) is computed.
  4. Pre-backup: Before any change is applied, a configuration backup of the device is taken, giving a rollback guarantee.
  5. Idempotent apply: Commands are generated with a “leave alone if present, add if missing” (add-if-missing) and “find and update” (set-by-find) logic. Even if the same reconcile runs twice, the result doesn’t change.
  6. Verification: The device is read again to confirm that drift is back to zero.

The practical value of idempotency is this: if a reconcile is cut off half-way by a network outage, there’s no panic: re-running the job completes the missing steps without repeating the ones already applied. A partial success is also explicitly marked as partial; even if one command blows up, the others keep being attempted and the outcome is reported honestly.

This discipline is the only sustainable way to keep firewall rules, VLAN configuration, or centralized wireless (CAPsMAN) settings consistent across hundreds of devices.

RouterOS v6 vs v7 differences: where automation spends the most code

Managing both RouterOS 6 and 7 devices with a single command template isn’t possible; the version differences have to be baked into the automation. The ones we run into most in the field:

Topic RouterOS v6 RouterOS v7
BGP /routing/bgp/peer /routing/bgp/connection
NTP primary-ntp / secondary-ntp (separate fields) servers= (comma-separated list)
Bridge VLAN filtering Limited / immature Fully supported
WireGuard Absent Present (arrived with v7)

The practical approach: once you connect to the device, read the RouterOS version first, determine the major version, and branch command generation accordingly. Fallbacks of the “try the v7 path, fall back to the v6 path” kind are inevitable. This is a layer that bloats the code but is indispensable in the field. If you treat the version migration as a service, our MikroTik Support page covers the RouterOS 6→7 migration separately.

Credentials and audit: security at scale

A system that holds access to not one but hundreds of devices is a far more valuable target if it’s breached. The minimum lines we hold:

  • Passwords are stored encrypted: Device passwords are not in the database as plaintext but as ciphertext with symmetric encryption (Fernet); the key is held in an environment variable and never goes into the repo. The password is decrypted in memory only at the moment a worker is about to connect to a device. (Honest limit: this is not a HashiCorp Vault / KMS, but an env-based solution, where security depends on a single key and this is an area for maturing.)
  • Audit log: Every job is recorded with “who triggered it” information (created_by: user / scheduler / automatic). Every device action (backup taken, reconcile applied/failed, reboot detected) is written to a separate event table with who-did-what-when detail.
  • Least privilege: On the application side there is role-based access (viewer by default, management operations require admin) and group-based multi-tenancy. On the device side, the SNMP community that the automation adds is made read-only (read-access=yes, write-access=no). (Narrowing the privileges of device-side management users, on the other hand, is an area we continually improve; honestly, the “least privilege” ideal is not always easy here.)

So how large is this scale, really?

We designed the platform with a target of 50,000+ devices; we architected it (queue, horizontal workers, distributed locks, circuit breaker) to handle that size. Being honest here matters: 50,000 is not a proven production figure, it’s a design target. The architecture being sized to handle this scale and it having actually been run at that scale are not the same thing; the latter can only be validated with real load.

The practical takeaway for you: at 10-20 devices, manual management or simple scripts are enough. Once you move to 100+ devices, to many branches, or to the position of a service provider managing MikroTiks for your customers, the architecture above (structured API + job queue + idempotent reconcile + audit) isn’t a “luxury” but a precondition for sustainability.

Should you build it yourself, or have it managed?

Everything in this article is implementable and can be built with open-source tools (Python, librouteros, Redis, PostgreSQL). But you need to see the honest picture: v6/v7 differences, idempotency, a reliable queue, circuit breakers, and secure credential management are a serious engineering investment, and their upkeep is ongoing.

If you’re going to build it with your own team, this article gives you a realistic roadmap and a list of pitfalls. If you don’t want to carry that load, you can have a large fleet of MikroTiks managed in a centralized and auditable way as a service from teams like ours; our Network Infrastructure and MikroTik Support pages look right here. In either case the key principle is the same: manage your devices not by hand, but with a repeatable and verifiable system.

Kaynaklar

  1. librouteros: Python client for the RouterOS API — PyPI / librouteros (2026)
  2. Official RouterOS API documentation — MikroTik (2026)
  3. Official MikroTik RouterOS documentation — MikroTik (2026)

Sıkça Sorulan Sorular

Should I use the RouterOS API or SSH?+

For bulk, programmatic management, RouterOS's binary API (8728/8729) is far better suited than SSH: it returns structured data, so you don't have to parse command output as text, and idempotent 'add/set/find' operations are supported directly. We keep SSH only for the narrow jobs the API can't cover; the most typical example is the `/export` output, which has no API equivalent.

Which port does the RouterOS API use?+

The plaintext API is on port 8728 and the TLS-secured API (api-ssl) is on 8729. For any management exposed to the internet, only 8729 (api-ssl) should be used and access should be limited to trusted sources; plain 8728 is only reasonable on a secure, internal management network. The API service is enabled under `/ip service` and restricted with an address filter.

How do you send a command to hundreds of MikroTiks at once?+

At scale, the right approach is not to open thousands of concurrent connections inside a single process; instead you generate a separate 'job' for each device, place them on a queue (we use Redis), and have many workers consume the queue in parallel. That way parallelism scales horizontally with the number of workers, one slow device doesn't lock up the system, and a per-device lock prevents two changes from colliding on the same device.

Is a bulk configuration change safe?+

When set up correctly, yes. Our flow: generate the desired state from a template, compare it against the actual state read from the device (diff), take a configuration backup before applying any change, apply commands idempotently (leave alone if present, add if missing), then read back and verify that the drift is zero. Thanks to idempotency, a half-finished job can be safely re-run.

Are the RouterOS v6 and v7 APIs the same?+

No, there are significant differences, and this is where automation spends the most code. For example, BGP lives under `/routing/bgp/connection` in v7 and `/routing/bgp/peer` in v6; NTP is given as a comma-separated `servers=` in v7 while v6 uses separate `primary-ntp`/`secondary-ntp` fields; bridge VLAN filtering matured with v7. Automation must read the device's RouterOS version and generate the command accordingly.

Profesyonel Destek mi Lazım?

Bu konuda yardıma ihtiyacın varsa yanındayız. Kurulum, konfigürasyon ve sorun giderme için ulaş.

PaylaşX/TwitterLinkedIn

İlgili Yazılar