Route Leaking Between VRFs on Cisco IOS: From BGP First Principles to Advanced Manipulation

If you’ve come to this from the FortiGate equivalent, the first thing to know is that on Cisco IOS the playbook is well-trodden and you have real choices. There’s a one-line static option, a fully-fledged MP-BGP option, and a long history of route-map machinery that lets you control exactly what crosses between VRFs. The hardest part isn’t finding the feature — it’s knowing which of the available knobs to turn for the result you actually want.

This guide covers four things in order: a short BGP refresher framed around what matters for VRF leak, a short VRF refresher with the same lens, the concrete leak configurations on IOS, and the advanced route-manipulation primitives that turn a working leak into a correct one.

Why Route Leak in the First Place

The patterns that force route leaking are the same on every platform — shared services across tenants, centralised internet breakout, post-acquisition overlapping address space, hub-and-spoke service insertion. The thing they have in common is that pure VRF isolation isn’t actually what you want. You want selective leakage of specific routes between specific VRFs, with the rest staying isolated. Anything you do here should be auditable later by a human reading the config and the routing table, not just functionally correct on the day.

BGP Background

You can leak routes between VRFs on IOS without BGP at all — ip route vrf X ... vrf Y ... is a thing — but the moment you want anything more sophisticated than a hand-coded static (filtering, conditional leak, scaling beyond a handful of prefixes, leak across a multi-PE fabric) you’re in MP-BGP territory. So a small amount of BGP literacy pays off enormously.

What BGP actually is. BGP is a path-vector protocol. Unlike OSPF and EIGRP, which describe topology and let each router compute SPF or DUAL on it, BGP describes paths — sequences of autonomous systems that a destination prefix has been advertised through. Every prefix in the BGP RIB has a list of attributes attached that travel with it; routers along the way modify those attributes and use them to make decisions.

The attributes that matter for leak design.

  • NEXT_HOP — the IP the packet should go to next. With route-leak across VRFs, NEXT_HOP behaviour is what makes recursion either work or fail.
  • AS_PATH — the list of ASNs the route has traversed. Used for loop prevention. When you leak between VRFs by using BGP, AS_PATH manipulation is one of the levers that decides whether the leaked route is preferred over a native one.
  • LOCAL_PREF — iBGP-only, higher wins. The cleanest knob for “prefer the route from this VRF over the equivalent from that VRF”.
  • MED — between AS-pairs, lower wins. Less relevant for intra-AS VRF design but you’ll see it.
  • COMMUNITIES — opaque tags. The most powerful classifier you have. Tag routes at ingress, match on tags everywhere else.
  • Extended communities — the family that contains Route Targets. RTs are extended communities. The bit most enterprise engineers don’t realise: the RT-based VRF leak isn’t a separate mechanism, it’s a community filter on top of MP-BGP.

The BGP decision process (the bit most candidates memorise for CCNP and then forget). When BGP has multiple paths to the same prefix:

  1. Highest WEIGHT (Cisco-local, not advertised)
  2. Highest LOCAL_PREF
  3. Locally originated (network/redistribute)
  4. Shortest AS_PATH
  5. Lowest origin (IGP < EGP < incomplete)
  6. Lowest MED
  7. eBGP over iBGP
  8. Lowest IGP cost to NEXT_HOP
  9. Older eBGP path
  10. Lowest router ID
  11. Lowest cluster list length
  12. Lowest neighbour address

For VRF design, the levers you’ll actually pull are LOCAL_PREF, AS_PATH (via prepend) and WEIGHT. Communities aren’t in the decision process directly — they’re how you classify routes so that a route-map applies a LOCAL_PREF or a prepend conditionally.

iBGP vs eBGP, and why MP-BGP changes things. Plain BGP carries IPv4 prefixes. MP-BGP (Multiprotocol BGP, RFC 4760) carries multiple address families in the same session: IPv4 unicast, IPv6 unicast, VPNv4, VPNv6, EVPN, and others. The VPNv4 address family is what makes VRF-aware BGP work. A VPNv4 prefix is RD:IPv4-prefix — the Route Distinguisher prepended to the IPv4 prefix to keep two overlapping 10.0.0.0/8s from clobbering each other in a single BGP RIB. That’s all RD does; it’s a uniquifier, not a filter. The filter is the RT.

That’s the BGP a network engineer needs in their head before reading the next section. Path-vector, attributes, decision process, and the fact that VPNv4 is just IPv4 with an RD bolted on the front.

VRF Background

A VRF (Virtual Routing and Forwarding instance) is a separate routing table on a single device. Same physical box, different RIBs, different FIBs, no shared forwarding state. Two VRFs can have overlapping address space and the device handles them independently because the VRF context is part of the lookup key.

On IOS there are two flavours of VRF that look similar but are configured differently:

Legacy VRF (ip vrf NAME) — the older syntax, IPv4-only.

VRF definition (vrf definition NAME) — the modern syntax, multi-address-family. This is what to use today on anything 15.x or newer.

The standard modern config:

vrf definition CORP
 rd 65001:10
 address-family ipv4
  route-target export 65001:10
  route-target import 65001:10
  route-target import 65001:99
 exit-address-family

Three concepts to be clear on:

Route Distinguisher (RD). A 64-bit value prepended to the IPv4 prefix to disambiguate it inside MP-BGP. It does not control which VRFs see the route — that’s the RT’s job. Two VRFs with the same RD but different RTs are perfectly valid. Conventionally <ASN>:<vrf-id> but you can pick any globally-unique-within-fabric value. There is one RD per VRF.

Route Target (RT) — Export. When BGP advertises a route from this VRF into the VPNv4 RIB, it stamps the route with this extended community. Multiple export RTs are allowed; the route gets tagged with all of them.

Route Target (RT) — Import. When BGP receives a VPNv4 route, it imports the route into this VRF’s IPv4 RIB if the route’s RT-export-list intersects with the VRF’s RT-import-list. Multiple import RTs are allowed and they’re OR’d — any match wins.

VRF-Lite vs full MPLS L3VPN. VRF-Lite means VRFs on a single device (or a few devices) without an MPLS core between them — interfaces are the boundary, no MP-BGP across PEs is required. Full MPLS L3VPN has MP-BGP between PEs, MPLS in the core, and the RT machinery is what scales the design across hundreds of sites. Route leaking on a single IOS box is VRF-Lite — same RT primitives, just nothing crossing a label-switched core.

Route leaking is RT manipulation. Once you’ve internalised that RT-export tags the route and RT-import filters which routes show up in this VRF, the rest of the leak config is mechanical.

Route Leaking on Cisco IOS: The Static Option

The shortest path to “I just want this one route to appear over there”:

ip route vrf BLUE 10.10.10.0 255.255.255.0 GigabitEthernet0/1 10.20.0.1

Add a static route into VRF BLUE with the next-hop interface and IP. If the next-hop’s egress interface lives in the global table, add the qualifier:

ip route vrf BLUE 10.10.10.0 255.255.255.0 10.20.0.1 global

global means the next-hop is in the global routing table, not in any VRF. To resolve through a named VRF use:

ip route vrf BLUE 10.10.10.0 255.255.255.0 vrf RED 10.20.0.1

When this is the right choice: leaking a couple of fixed prefixes and you’ve decided the auditability of an explicit static line is worth more than the elegance of a policy-driven leak. When it’s the wrong choice: anything dynamic, anything that needs to react to upstream state, anything that scales past about a dozen prefixes.

The static option doesn’t traverse BGP, doesn’t carry AS_PATH or community, can’t be matched on by route-maps elsewhere, and can’t be filtered by upstream peers. It is what it is — a pinned next-hop into another VRF.

Route Leaking on Cisco IOS: The MP-BGP Option

This is the design that scales and the one to default to in any environment that already runs BGP for anything.

The minimum working configuration to leak between VRFs RED (10.10.0.0/16) and BLUE (10.20.0.0/16) on a single box:

vrf definition RED
 rd 65001:10
 address-family ipv4
  route-target export 65001:10
  route-target import 65001:20
 exit-address-family
!
vrf definition BLUE
 rd 65001:20
 address-family ipv4
  route-target export 65001:20
  route-target import 65001:10
 exit-address-family
!
interface GigabitEthernet0/0
 vrf forwarding RED
 ip address 10.10.0.1 255.255.255.0
!
interface GigabitEthernet0/1
 vrf forwarding BLUE
 ip address 10.20.0.1 255.255.255.0
!
router bgp 65001
 bgp log-neighbor-changes
 !
 address-family ipv4 vrf RED
  redistribute connected
 exit-address-family
 !
 address-family ipv4 vrf BLUE
  redistribute connected
 exit-address-family

What’s happening, line by line, is worth unpicking because the MP-BGP-on-a-single-box case is the one most introductions skip.

vrf definition RED declares the VRF. rd 65001:10 makes its routes uniquely identifiable inside the VPNv4 RIB. route-target export 65001:10 says “tag everything I send into VPNv4 with this RT”. route-target import 65001:20 says “import any VPNv4 route I see tagged with that RT”.

router bgp 65001 / address-family ipv4 vrf RED / redistribute connected is the load-bearing block most beginners miss. Just having the VRF, RD and RT does nothing on its own — you need to tell BGP to take routes from VRF RED’s IPv4 RIB and originate them as VPNv4. redistribute connected is the simplest source. You can also redistribute static, redistribute an IGP (redistribute ospf 1 etc.), or network specific prefixes.

The same three lines under VRF BLUE wire it up symmetrically. Both VRFs export their connected routes as VPNv4, both import each other’s RT, the routes appear in each other’s IPv4 RIBs.

To verify, the commands are VRF-aware versions of the usual ones:

show ip route vrf RED
show ip route vrf BLUE
show ip bgp vpnv4 vrf RED
show ip bgp vpnv4 all
show vrf detail

You should see 10.20.0.0/24 appear in show ip route vrf RED as a B (BGP) route, with NEXT_HOP being the IP on the BLUE-side interface.

Asymmetric leak. The configuration above is symmetric — both directions. For a one-way leak (e.g., shared-services VRF visible to tenants but not vice versa), drop the import RT on one side. SHARED-SERVICES exports 65001:99, every tenant imports 65001:99. Tenants don’t export anything to SHARED-SERVICES, so SHARED-SERVICES doesn’t import anything from them. Done.

Fan-out shared services. With multiple tenants you don’t want to maintain a list of RTs per VRF. The pattern is one well-known RT for “shared services”:

vrf definition SHARED-SERVICES
 rd 65001:99
 address-family ipv4
  route-target export 65001:99
  route-target import 65001:99
 exit-address-family
!
vrf definition TENANT-A
 rd 65001:101
 address-family ipv4
  route-target export 65001:101
  route-target import 65001:99
  route-target import 65001:101
 exit-address-family
!
vrf definition TENANT-B
 rd 65001:102
 address-family ipv4
  route-target export 65001:102
  route-target import 65001:99
  route-target import 65001:102
 exit-address-family

Each tenant exports with their own unique RT, imports the well-known shared-services RT. SHARED-SERVICES exports its one RT, imports nothing tenant-specific (so it doesn’t see tenant-internal routes). Add a tenant by copying the pattern; no VRF other than the new one is touched.

Advanced Route Manipulation

A working leak is a baseline. The interesting work — the part that distinguishes a design that holds up under M&A, audit, and 3am incidents from one that doesn’t — is the manipulation layer. These are the primitives, in roughly the order you reach for them.

Route-maps

Route-maps are the spine of policy on IOS. A route-map is an ordered list of clauses; each clause has match conditions and set actions; the first clause whose match conditions all hit decides the action. They attach to redistribution, to neighbour configuration, and — relevantly here — to VRF import and export.

Apply a route-map to filter what an RT export emits:

vrf definition RED
 rd 65001:10
 address-family ipv4
  route-target export 65001:10
  route-target import 65001:20
  export map RM-EXPORT-RED
 exit-address-family

And the route-map itself:

route-map RM-EXPORT-RED permit 10
 match ip address prefix-list PL-SHARED-SUBNETS
 set extcommunity rt 65001:10
!
route-map RM-EXPORT-RED deny 99

The implicit deny at the end of every route-map is real and bites — be explicit if you want clarity in the config.

import map does the same thing on the import side, useful when you want to restrict not what you advertise but what you accept. Both can apply set actions: rewrite communities, set local-pref, prepend AS_PATH.

Prefix Lists vs Access Lists

For matching prefixes, prefix-lists are the right tool — ACLs work but the syntax for prefix-length matching with ge and le keywords is much cleaner:

ip prefix-list PL-SHARED-SUBNETS seq 10 permit 10.10.10.0/24
ip prefix-list PL-SHARED-SUBNETS seq 20 permit 10.10.20.0/24
ip prefix-list PL-LARGER-AGG seq 10 permit 10.0.0.0/8 ge 16 le 24

The ge/le are inclusive bounds on prefix length, so 10.0.0.0/8 ge 16 le 24 matches anything within 10.0.0.0/8 with a mask between /16 and /24.

AS-Path Access Lists

Match on AS_PATH using regular expressions:

ip as-path access-list 10 permit ^$
ip as-path access-list 10 permit _65001_
ip as-path access-list 20 deny .*

^$ is an empty AS_PATH (locally originated). _65001_ matches AS 65001 anywhere in the path (the underscores are word boundaries on IOS). Useful when you want to leak only routes that originated locally, not transit routes that came in from somewhere else.

In a route-map:

route-map RM-LOCAL-ONLY permit 10
 match as-path 10

Communities

Standard communities are 32-bit values, conventionally written ASN:value. Set them at ingress, match them everywhere else.

ip community-list standard CL-CORE permit 65001:100
ip community-list standard CL-DMZ  permit 65001:200
!
route-map RM-TAG-CORE permit 10
 match ip address prefix-list PL-CORE-PREFIXES
 set community 65001:100 additive
!
route-map RM-EXPORT-CORP permit 10
 match community CL-CORE
 set local-preference 200

additive is critical: without it, set community replaces existing community values. Almost always you want to add to whatever’s already there.

Extended communities include Route Targets, Site of Origin (SoO), and the Cost community. RT we’ve covered. SoO is a per-site tag used to prevent route looping in dual-homed sites — in a multi-PE design, mark routes with the originating site’s SoO and reject routes carrying your local SoO on import. The Cost community is rarely needed but lets you tweak the BGP best-path decision deterministically.

Large communities (RFC 8092) are 96-bit and break the 16-bit ASN cap of standard communities. If you’re operating with 4-byte ASNs you’ll want them. Syntax mirrors standard communities with ip large-community-list and match large-community.

LOCAL_PREF and AS-Path Prepend

The two most common policy actions in route-maps:

route-map RM-PREFER permit 10
 match ip address prefix-list PL-PRIMARY
 set local-preference 200
!
route-map RM-DEPREFER permit 10
 set as-path prepend 65001 65001 65001

LOCAL_PREF is the cleanest preference signal inside an AS — pure number, higher wins, no side effects. AS-path prepend is for advertisements going out to other ASes (or peers that don’t honour LOCAL_PREF), where you want to make a path look longer.

For VRF design within one AS, reach for LOCAL_PREF first and prepend only when you specifically need the path-length to influence a downstream decision.

MED and the Lesser-Used Knobs

MED is for indicating which of multiple eBGP entry points to your AS a peer should prefer; lower wins. For VRF leak it rarely matters. Where you do see it: when leaking routes out to an external peer that re-advertises into another network, MED is the signal that survives the AS boundary that LOCAL_PREF doesn’t.

set weight is a Cisco-proprietary, never-advertised, locally-significant integer. Highest in the BGP decision process. Useful for “always prefer the route I learned from this peer”, but invisible to anything else.

Continue Clauses

By default, route-map evaluation stops at the first matching clause. continue N lets a clause apply its set actions and then continue evaluation at clause N. This is one of those features that’s a lifesaver when you need it and a footgun when you abuse it. Use it sparingly and document liberally.

route-map RM-COMPLEX permit 10
 match ip address prefix-list PL-CORP
 set community 65001:100 additive
 continue 30
!
route-map RM-COMPLEX permit 20
 match ip address prefix-list PL-DMZ
 set community 65001:200 additive
!
route-map RM-COMPLEX permit 30
 set local-preference 200

Conditional Advertisement

neighbor X.X.X.X advertise-map RM-A non-exist-map RM-B advertises routes matched by RM-A only when no route matched by RM-B is in the BGP RIB. The classic use is “advertise a backup route only when the primary disappears”, but it works inside VRF leak design when you want VRF B to import a less-preferred route from VRF A only when VRF B’s normal source is down.

Aggregation and Suppression

aggregate-address summarises in BGP. summary-only suppresses the more-specifics. suppress-map selectively suppresses some more-specifics while letting others leak. In a multi-VRF design with significant prefix counts, aggregate at the VRF boundary to keep the receiving VRF’s table sane:

router bgp 65001
 address-family ipv4 vrf SHARED-SERVICES
  aggregate-address 10.99.0.0 255.255.0.0 summary-only

The receiving VRFs see one /16 instead of however many /24s and /25s SHARED-SERVICES is internally fragmented into.

Pitfalls

A short list of the things that catch people out on real networks.

RT mismatch. Configure both export and import or nothing happens. show ip bgp vpnv4 all is your friend — if the prefix is in the VPNv4 RIB but not in the receiving VRF’s IPv4 RIB, the import RT isn’t matching the export RT. This is the single most common cause.

Mutual import loops. Two VRFs both importing each other’s full RT can produce surprising results when one VRF originates a prefix, the other learns it, redistributes back, and the originating VRF sees its own prefix come back via BGP. SoO communities or AS_PATH filtering prevents this in larger fabrics; for two VRFs on one box, a tight prefix-list on import-map keeps it boring.

Recursive next-hop in the wrong VRF. A leaked route’s NEXT_HOP is in the source VRF, but lookups happen in the receiving VRF’s table. IOS handles this correctly when MP-BGP carries the route — the NEXT_HOP is resolved in the source VRF’s RIB. Where it goes wrong is when someone redistributes a static into BGP without thinking through which VRF the next-hop will be resolved in. Test with show ip cef vrf X exact-route SRC DST — that gives you the FIB-level decision, not just the RIB.

redistribute connected is broader than you think. It picks up every connected interface in the VRF. If you only want some, use redistribute connected route-map RM-CONNECTED-FILTER or network specific prefixes. The former is more flexible; the latter is more explicit.

iBGP and next-hop-self. When MP-BGP carries leaked routes between PEs across an MPLS core, the NEXT_HOP is the originating PE’s loopback. On a single VRF-Lite box this isn’t an issue, but the moment you scale across multiple devices, get next-hop-self right or routes will not install. neighbor X.X.X.X next-hop-self on iBGP sessions in the core.

VRF-aware diagnostic commands. The default ping is global-VRF. To ping from a VRF: ping vrf RED 10.20.0.1 source GigabitEthernet0/0. Same for traceroute, telnet, ssh. show ip route defaults to global; show ip route vrf RED to a VRF. The number of times an engineer pings a leaked address from the global table, sees no response, and concludes the leak doesn’t work — high. Always specify the VRF.

Soft-reconfig and clear ip bgp soft. When you change a route-map, the change applies only to new advertisements. To re-evaluate existing routes: clear ip bgp vpnv4 unicast * soft in (re-applies inbound policies) or ... soft out (re-evaluates outbound). Without this you’ll change the policy and watch nothing happen.

Auto-RT and route-target import auto. Some inter-AS scenarios use auto-derived RTs from the RD. Powerful but obscure — avoid in enterprise designs unless you’re matching a specific operator pattern.

Closing

If your environment is a single FortiGate, the route-leak post on FortiGate covers the equivalent design problem on that platform — the conclusions are different precisely because Cisco gives you a static option and FortiGate doesn’t.

On IOS, the muscle memory worth building is: VRF for isolation, RT for which routes leak where, route-map for what crosses and how, prefix-list for what to match on, community for how to classify, LOCAL_PREF for how to prefer. The static ip route vrf X ... vrf Y exists for the simple case, and is the right answer roughly as often as a sticky-note on a monitor is — useful, but not the structural solution.

If you take one thing from this post: route leak isn’t a feature, it’s a composition of rd, route-target, route-map and redistribute. Each one does a small, well-defined thing. Once you’ve internalised that, the configuration writes itself.