Berik Ashimov // senior.it.engineer
Download CV
POSTS / MIKROTIK

BGP on RouterOS 7: The Peer Is Dead, Long Live the Connection

// series · MikroTik RouterOS 7 Guide part 13 of 35
  1. ... 10 earlier
  2. Policy-Based Routing on RouterOS 7: Tables First, Marks Second
  3. OSPF on RouterOS 7: Templates, Not Network Statements
  4. BGP on RouterOS 7: The Peer Is Dead, Long Live the Connection
  5. Routing Filters in RouterOS 7: A Real Language, With One Nasty Trap
  6. VRFs on RouterOS 7: Real Separation, Not Firewall Hope
  7. ... 20 later
view the full series →

BGP is the feature that justified rewriting RouterOS routing from scratch. The v6 implementation was single-threaded and infamous for it: a full table on a CCR meant minutes of convergence while one CPU core melted and thirty-five others watched. MikroTik’s answer in v7 wasn’t a patch — it was a new routing process with a new configuration model to match.

The model change is blunt: /routing bgp peer no longer exists. Configuration now lives in templates (reusable parameter sets) and connections (the actual sessions). Networks aren’t announced from a network menu anymore — that’s gone too, replaced by output.network pointing at a firewall address-list. If you try to type your v6 muscle memory into a v7 box, nothing autocompletes, and that’s the point.

This article builds eBGP and iBGP the v7 way, covers the parameters that actually matter — roles, multihop, prefix limits — and explains why the new stack finally makes MikroTik a serious BGP platform. Filtering gets the next article to itself; here I’ll wire the hooks and leave them loaded.

Instances, Templates and Connections

Recent 7.x releases settled the object model into three layers. An instance is your BGP identity — AS number and router ID — and every session belongs to one. A template is an optional bag of shared session parameters (address families, filter chains) for keeping multi-peer configs DRY. A connection is one BGP session, referencing the instance and optionally inheriting a template.

Terminal window
# The identity — this is where the router ID lives in current v7
/routing bgp instance add name=as65500 as=65500 router-id=10.255.0.1
# Optional shared parameters for sessions
/routing bgp template add name=defaults afi=ip

afi= takes ip (IPv4 unicast), ipv6, and the VPN families. Note it’s ip, not ipv4 — a small trap for people arriving from other vendors; in older 7.x this parameter was spelled address-families, so expect both spellings in forum posts. Sessions negotiate only the families both ends configure.

Pin router-id to your loopback address on the instance and be done with it — a router ID that floats with whatever address is up is a subtle way to reset every session when a WAN flaps.

eBGP to an Upstream

AS 65500, single upstream at AS 64600, announcing 203.0.113.0/24:

Terminal window
# 1. What we announce: an address-list, not a network statement
/ip firewall address-list add list=bgp-networks address=203.0.113.0/24
# 2. A route for it must exist (BGP won't announce what isn't routed)
/ip route add dst-address=203.0.113.0/24 blackhole comment="BGP anchor"
# 3. The session
/routing bgp connection add name=upstream1 instance=as65500 templates=defaults \
remote.address=198.51.100.1 remote.as=64600 local.role=ebgp \
output.network=bgp-networks

Three things replaced the v6 network statement here. The address-list (output.network=bgp-networks) defines candidate prefixes; the blackhole anchor route makes each prefix valid to announce (exactly the same discipline as on any other vendor — announce only what you can route); and the connection ties them together. Add a prefix to the list, make sure it’s routed, and it’s announced. It’s more explicit than v6 and much harder to fat-finger into announcing a /8.

local.role=ebgp isn’t decoration. Roles (ebgp, ebgp-customer, ebgp-provider, ebgp-peer, ebgp-rs, ibgp, ibgp-rr, …) tell the stack what kind of relationship this is — and the RFC 9234-style roles enable leak prevention semantics on top. At minimum, ebgp vs ibgp decides AS-path behavior, next-hop handling, and multihop defaults. Set it correctly on every connection.

Do not bring this session up without filters. An eBGP session with no input.filter accepts everything the peer sends — including a full table or a leak of someone else’s routes. I said it in the VyOS BGP article and it’s just as true here: filters are not optional. The hooks look like this, and the next article fills the chains in:

Terminal window
/routing bgp connection set upstream1 \
input.filter=upstream-in output.filter-chain=upstream-out

Prefix Limits: Your Blast Shield

The v7 spelling of max-prefix is input.limit-process-routes-ipv4 (and -ipv6):

Terminal window
# Upstream sends default-only? Cap it hard.
/routing bgp connection set upstream1 input.limit-process-routes-ipv4=10
# Full-table peer (~1M IPv4 routes in 2026): cap with headroom
/routing bgp connection set upstream1 input.limit-process-routes-ipv4=1200000

Semantics differ from Cisco-style max-prefix, so read carefully: when the limit is hit, RouterOS stops processing further routes and flags the session — it does not tear the session down. Recovery is manual: after fixing the cause, clear the session:

Terminal window
/routing bgp session print
/routing bgp session clear [find name~"upstream1"]

The limit is per-address-family and counts received routes, not installed ones. It’s a blast shield, not a substitute for input filtering — a peer can still send you 9 garbage routes under a limit of 10.

iBGP

iBGP in v7 is the same objects with local.role=ibgp and the usual iBGP disciplines: peer between loopbacks, and remember iBGP doesn’t rewrite next-hop.

Terminal window
# Loopback peering — survives any single link failure if the IGP has a path
/routing bgp connection add name=rr1 instance=as65500 \
local.address=10.255.0.1 local.role=ibgp \
remote.address=10.255.0.2 remote.as=65500 \
multihop=yes nexthop-choice=force-self

local.address= is v7’s update-source. multihop=yes is required for loopback iBGP — eBGP defaults to TTL 1 semantics and loopback-to-loopback is more than one hop; the same flag serves eBGP-multihop setups (route servers, providers peering from a distant router). nexthop-choice=force-self is next-hop-self: without it, routes learned from your upstream get advertised to iBGP neighbors with an external next-hop they can’t resolve, and the routes stay inactive on the far end.

Full mesh scales as N(N-1)/2, so past a handful of routers make one or two boxes route reflectors — that’s just local.role=ibgp-rr on the reflector’s connections toward its clients, ibgp on the clients. No separate cluster configuration for the basic case.

For session security on eBGP, tcp-md5-key= gives you RFC 2385 TCP MD5 if the other side requires it, and use-bfd=yes gets you sub-second failure detection instead of waiting out the hold timer.

Verification

The session view is the first stop:

Terminal window
/routing bgp session print
# name="upstream1-1" remote.address=198.51.100.1 remote.as=64600
# local.role=ebgp uptime=2h3m ...

Note sessions are dynamic objects created from connections — a connection that never produces a session means TCP 179 isn’t completing (firewall, wrong address, no route to peer).

What did they send us, and what did we install?

Terminal window
# Everything BGP knows, including filtered-out routes
/routing route print where bgp
# What actually made the FIB
/ip route print where bgp

What are we announcing? This one has a v7 quirk: to inspect sent advertisements after the fact, the session must store them, which costs memory and is off by default:

Terminal window
/routing bgp connection set upstream1 output.keep-sent-attributes=yes
/routing bgp session print
/routing bgp session dump-saved-advertisements [find name~"upstream1"]
# writes the current Adj-RIB-Out to a file for inspection

Turn keep-sent-attributes on while you’re building or debugging, off on memory-constrained boxes in steady state. For quick checks, /routing bgp advertisements print shows the current outgoing updates without the dump machinery.

Why v7 BGP Finally Scales

The v6 routing process was a single-threaded monolith: one core did best-path for every peer, every update, every reconvergence. The v7 routing stack is a ground-up rewrite that spreads work across cores — sessions, filtering, and route processing no longer serialize behind one CPU.

In practice, on multi-core hardware (CCR2004/CCR2116/CCR2216 class), that’s the difference between a full IPv4+IPv6 table converging in minutes and converging in seconds, and between “touching a filter reloads the world” and incremental updates. Add the honest config model — explicit address families, roles, per-AFI limits — and v7 is the first RouterOS you can defend running at an IXP or as a multi-homed edge without a stack of caveats. RAM still matters: budget roughly a few hundred MB per full-table peer with attributes retained, which rules out the 256 MB boxes for full tables — take a default plus longer-than-/22s instead on those.

What v7 BGP is not is finished in every corner: exotic knobs from big-iron vendors (per-neighbor ORF, some add-path modes) are still absent or young. For transit, peering, and iBGP cores, it’s solid.

Closing Thoughts

The v7 checklist:

  1. Instance for identity (as, router-id), optional template for shared parameters (afi=ip), connections for sessions.
  2. output.network address-list + anchor route replaces the network statement.
  3. local.role on every connection; multihop=yes and local.address for loopback peering; nexthop-choice=force-self toward iBGP.
  4. input.limit-process-routes-ipv4 on every eBGP session — and remember it flags, it doesn’t tear down; session clear to recover.
  5. keep-sent-attributes when debugging announcements.

Everything here assumed the filter chains exist. They don’t yet — and an unfiltered BGP session is an outage on a timer. Next article: the v7 routing filter language, where those upstream-in and upstream-out chains get built properly.

// share