Skip to content
cs-routeros-bouncer logocs-routeros-bouncer logo

RouterOS Bouncer

cs-routeros-bouncer syncs CrowdSec decisions into MikroTik firewall rules with reconciliation, metrics, and safe cleanup.
  1. CrowdSec decidesThe Local API holds a decision about an address.
  2. The bouncer syncsIt reads new decisions and expires the ones that ended.
  3. RouterOS enforcesAddresses land on a firewall address list the managed rules match.
  4. You watch itPrometheus metrics, a health endpoint and a Grafana dashboard.

Why cs-routeros-bouncer? 1.5.0

Section titled “Why cs-routeros-bouncer? ”

Most MikroTik bouncers regenerate an address list from a scheduled script. This one keeps a session open and applies each CrowdSec decision as a single call to the RouterOS API.

No manual router commands

It writes the 8 rules a stock configuration needs when it starts and removes them when it stops. You configure the bouncer, not the router.

Per-decision writes (~1–3 ms/op)

One address added on ban, one removed on unban, at roughly 1–3 ms each. No bulk re-upload, no duplicates.

Reconciliation, not hope

At startup and on an interval it compares the Local API against what is actually on the router, then adds what is missing and removes what is stale.

Observable while it runs

Prometheus metrics for decisions, RouterOS CPU and dropped traffic; structured logs; a health endpoint; a Grafana dashboard in the repository.

One binary, MIT-licensed

Built with Go 1.27 and published as 16 prebuilt binaries across 4 operating systems, plus multi-architecture Docker images.

CrowdSec decides which addresses are hostile. This bouncer enforces that decision and does nothing else — no detection, no scoring, no opinion of its own. Enforcing means writing to a production firewall, so here is the whole of what it writes, and the whole of what it will not clean up for you.

What it writes

A stock configuration puts 8 rules on the router: 4 kinds, one set per protocol family. These, verbatim.

Rules a stock configuration writes
/ip/firewall/filter add chain=input action=passthrough comment="crowdsec-bouncer:filter-input-counting-v4 @cs-routeros-bouncer"
/ip/firewall/filter add chain=input action=drop src-address-list=crowdsec-banned comment="crowdsec-bouncer:filter-input-input-v4 @cs-routeros-bouncer"
/ip/firewall/raw add chain=prerouting action=passthrough comment="crowdsec-bouncer:raw-prerouting-counting-v4 @cs-routeros-bouncer"
/ip/firewall/raw add chain=prerouting action=drop src-address-list=crowdsec-banned comment="crowdsec-bouncer:raw-prerouting-input-v4 @cs-routeros-bouncer"
/ipv6/firewall/filter add chain=input action=passthrough comment="crowdsec-bouncer:filter-input-counting-v6 @cs-routeros-bouncer"
/ipv6/firewall/filter add chain=input action=drop src-address-list=crowdsec6-banned comment="crowdsec-bouncer:filter-input-input-v6 @cs-routeros-bouncer"
/ipv6/firewall/raw add chain=prerouting action=passthrough comment="crowdsec-bouncer:raw-prerouting-counting-v6 @cs-routeros-bouncer"
/ipv6/firewall/raw add chain=prerouting action=drop src-address-list=crowdsec6-banned comment="crowdsec-bouncer:raw-prerouting-input-v6 @cs-routeros-bouncer"

Banned addresses go on crowdsec-banned and crowdsec6-banned. The two passthrough rules match no address list and take no decision; they count the packets the block evaluates, which is what makes “evaluated” and “dropped” two separate numbers in the metrics.

What it does not do

It enforces; it does not decide
Every ban comes from CrowdSec. Take CrowdSec away and this has nothing to write: no scenarios, no scoring, no address of its own. How a decision becomes a rule
One decision type, not four
Of the CrowdSec decision types, ban is the only one enforced by default. The bouncer has exactly one action — put the address on a list the firewall drops — so a captcha decision could be listed in crowdsec.supported_decisions_types but would be enforced as a block, which is not what a captcha means. The setting is there for custom decision types that do mean “block”. How decisions are processed
The rules go in at the top
By default the managed block is inserted at the top of every chain it touches, above your existing rules: firewall.rule_placement.strategy is top by default. For an operator whose ruleset is ordered deliberately this is the most consequential thing the bouncer does to it — so it is worth saying that placement is the most configurable thing here, not the least: five strategies (top, bottom, before_comment, after_comment, position), each overridable per table and per protocol family, with a fallback for when the anchor rule is missing. Rule placement
The first sync is a bulk import
The first reconciliation pulls every active decision the Local API holds, CrowdSec's CAPI community blocklists included: crowdsec.origins is empty by default, so no origin is filtered out. That is tens of thousands of addresses written in one pass, and on a small router you will watch it happen in the CPU graph. CAPI blocklists
Every reconciliation pass costs the router CPU
Not only at startup, and not only when there is drift to repair: the pass runs on every crowdsec.reconciliation_interval tick and re-reads the whole address list each time, because RouterOS evaluates address-list queries with an unindexed linear scan. Measured on an RB5009 holding 22,000 entries, sampled at 100 ms on the router itself: a ~2-second plateau averaging 31% against a 5% baseline, with single-core slices reaching 50–100%, in every cycle. At the default 15-minute interval that is four transients an hour. Your own monitoring may well not show it — the standard SNMP hrProcessorLoad OID reports a one-minute average, which flattens a two-second spike to roughly 6%. Performance tuning
What the daemon leaves behind
Shutdown removes every rule above and no address-list entry. Entries expire on their own MikroTik timeout, which is the CrowdSec decision duration; protection therefore outlives the daemon by whatever is left of it.Caveat: A decision whose duration resolves to zero or less — CrowdSec does emit negative remaining durations — is written with no timeout at all, so that entry stays on the router until something removes it: the next reconciliation, or you. A decision carrying no duration field is discarded earlier and never reaches the router.
  • CrowdSec 1.5+ with the Local API reachable from the host running the bouncer
  • MikroTik RouterOS 7.x with the API service enabled (port 8728, or 8729 for TLS)
  • A dedicated RouterOS API user with permission to read and write firewall rules and address lists
What is cs-routeros-bouncer?

cs-routeros-bouncer is a free, open-source CrowdSec bouncer for MikroTik RouterOS. It syncs CrowdSec ban/unban decisions into RouterOS firewall rules (filter and raw, IPv4 and IPv6) through the RouterOS API, with startup and periodic reconciliation, Prometheus metrics, and safe rule cleanup.

Which CrowdSec and RouterOS versions does it support?

It requires CrowdSec 1.5+ with the Local API (LAPI) reachable from the bouncer host, and MikroTik RouterOS 7.x with the API service enabled (port 8728, or 8729 for TLS), using a dedicated RouterOS API user with the appropriate permissions.

Is cs-routeros-bouncer free and open source?

Yes. cs-routeros-bouncer is MIT-licensed, written in Go, distributed as a single static binary, with the full source on GitHub and no paid tier.

Does cs-routeros-bouncer support IPv6?

Yes. Each RouterOS rule type it manages (filter input, raw prerouting, and optional filter output) has an IPv6 equivalent, and IPv4/IPv6 rule placement can be configured together or overridden independently per protocol.

How is cs-routeros-bouncer different from address-list or script-based CrowdSec bouncers for MikroTik?

cs-routeros-bouncer talks to the RouterOS API directly and applies each ban or unban as an individual real-time call (about 1–3 ms), instead of periodically regenerating address lists via scheduled scripts. It also runs startup and periodic reconciliation against MikroTik's actual state, so drift is repaired automatically.