FlexVPN is what happens when Cisco stops bolting features onto crypto maps and rebuilds the whole thing on top of IKEv2. There is no crypto ACL, no static tunnel per peer, and no routing protocol running over the overlay just to tell the hub where a spoke's LAN lives. You define one authorization policy, one Virtual-Template, and the hub clones a fresh Virtual-Access interface for every spoke that authenticates, hands it an IP address, and installs its routes. If you want the conceptual background first, start with the IPsec VPN pillar and FlexVPN explained. This article is the other half: the build.
Everything below came out of a live CML lab. Three cat8000v routers on IOS XE 17.18.02 (HUB, SPOKE1, SPOKE2) with an IOL-XE router acting as the ISP between them. Every command and every piece of output on this page is real, including the failure we walked into and the log lines it produced.
The Lab
203.0.113.110.20.10.0/24192.168.99.0/24 (real Debian VM)198.51.100.210.30.10.0/24spoke1.pinglabz.lab198.51.100.610.30.20.0/24spoke2.pinglabz.labThe spokes have a default route to the ISP and nothing else. They do not know the hub's LAN exists. By the end of this build they will, and no routing protocol will have been involved.
Block 1: AAA and the Authorization Policy
This is the block people skim, and it is the single most important block in the entire configuration. FlexVPN's headline feature is that the hub pushes an IP address and a set of routes down to the spoke as part of the IKEv2 exchange. The mechanism it uses to do that is AAA network authorization. So the first thing you turn on is AAA.
aaa new-model
aaa authorization network PLZ-FLEX-LIST localaaa authorization network creates a method list called PLZ-FLEX-LIST that resolves against the local database (in production this would point at RADIUS instead). When the IKEv2 profile later says "go ask AAA what this peer is allowed to have", PLZ-FLEX-LIST is what it asks.
What AAA hands back is the authorization policy:
crypto ikev2 authorization policy PLZ-AUTHOR-POL
pool PLZ-FLEX-POOL
route set interface
route set access-list PLZ-FLEX-ROUTESThree lines, three jobs:
pool PLZ-FLEX-POOLtells the hub to allocate the connecting spoke an address out of a local pool. That address becomes the spoke's tunnel IP.route set interfaceadvertises the interface the Virtual-Access is unnumbered to (on our hub, Loopback0,10.255.0.1). That is how the spoke learns the hub's tunnel endpoint address.route set access-list PLZ-FLEX-ROUTESadvertises whatever the ACL permits. This is the hub telling the spoke "here are the networks I can reach for you."
And the ACL itself:
ip access-list standard PLZ-FLEX-ROUTES
permit 10.20.10.0 0.0.0.255
permit 192.168.99.0 0.0.0.255
permit 10.30.0.0 0.0.255.255The first two lines are the hub's own networks (the LAN and the segment where our real Debian VM lives). The third, 10.30.0.0/16, is the summary that covers both spoke LANs. That is what makes spoke-to-spoke traffic possible at all: SPOKE1 installs a route for 10.30.0.0/16 pointing at the tunnel, so when it wants to reach SPOKE2's 10.30.20.0/24 it sends the packet to the hub.
This block is the heart of FlexVPN. Get it right and the rest is plumbing. Skip it and you have built an ordinary SVTI with extra steps.
Block 2: The Address Pool
ip local pool PLZ-FLEX-POOL 10.0.9.10 10.0.9.100Ninety one addresses, one per spoke. Every spoke that authenticates gets one, and it gets it back into the pool when the session tears down. This is a plain ip local pool, the exact same construct used by remote-access VPN and PPP, which is a nice reminder that FlexVPN treats a site-to-site spoke as just another client.
Block 3: The IKEv2 Keyring (and an honest note about wildcard PSKs)
crypto ikev2 keyring PLZ-FLEX-KEYRING
peer SPOKES
address 0.0.0.0 0.0.0.0
pre-shared-key PingLabz-IKEv2-PSK-01One keyring entry, matching 0.0.0.0 0.0.0.0, which is every address on the internet. This is deliberate. The entire point of a FlexVPN hub is that you do not have to touch it when you add spoke number 47. If the keyring had to name each peer by IP, you would be back to editing the hub for every new site, and the dynamic Virtual-Template would be pointless.
Be honest with yourself about what that costs. A wildcard pre-shared key means every spoke shares one secret, and any device that learns that secret and can present an acceptable identity can bring up a tunnel to your hub. It is fine for a lab and it is fine for a proof of concept. In production, the answer is certificates: each spoke gets its own identity certificate from your CA, the hub validates it, and revoking one spoke does not mean rekeying the fleet. We use a wildcard PSK here because it keeps the config readable and the failure modes visible, not because it is what you should ship.
Block 4: The IKEv2 Profile
crypto ikev2 profile PLZ-FLEX-PROF
match identity remote fqdn domain pinglabz.lab
identity local fqdn hub.pinglabz.lab
authentication local pre-share
authentication remote pre-share
keyring local PLZ-FLEX-KEYRING
aaa authorization group psk list PLZ-FLEX-LIST PLZ-AUTHOR-POL
virtual-template 1The profile is where the hub decides who it will talk to and what happens when it does. Four things are worth stopping on.
Identity matching by FQDN domain. match identity remote fqdn domain pinglabz.lab says: accept any peer whose IKE identity ends in pinglabz.lab. Not an IP address, a domain. That is the second half of what makes the hub spoke-agnostic. SPOKE1 announces itself as spoke1.pinglabz.lab, SPOKE2 as spoke2.pinglabz.lab, and a spoke you build next month as spoke47.pinglabz.lab, and the hub matches all of them against one line. (If you are hazy on how IKEv2 identities and the SA exchange actually work, IKEv2 explained covers it.)
The AAA line. aaa authorization group psk list PLZ-FLEX-LIST PLZ-AUTHOR-POL is the wire that connects Block 1 to the tunnel. It says: for PSK-authenticated peers, run group authorization against method list PLZ-FLEX-LIST, using PLZ-AUTHOR-POL as the username to look up. Without this line, IKEv2 will still authenticate, the tunnel will still come up, and absolutely nothing will be pushed to the spoke.
virtual-template 1. This tells IKEv2 which template to clone when a peer matches this profile. This is the dynamic part.
Block 5: The IPsec Profile
crypto ipsec profile PLZ-FLEX-IPSEC
set transform-set PLZ-TS-GCM
set ikev2-profile PLZ-FLEX-PROFShort and mechanical. The IPsec profile binds the data-plane transform (we are using esp-gcm 256) to the IKEv2 profile that governs the control plane. The Virtual-Template applies this profile, and every Virtual-Access cloned from that template inherits it.
Block 6: The Virtual-Template (read this part twice)
interface Virtual-Template1 type tunnel
ip unnumbered Loopback0
ip nhrp network-id 1
ip nhrp redirect
tunnel source GigabitEthernet2
tunnel mode ipsec ipv4
tunnel protection ipsec profile PLZ-FLEX-IPSECThe Virtual-Template is a blueprint, not an interface that carries traffic. When SPOKE1 authenticates, IOS XE stamps out Virtual-Access1 from it. When SPOKE2 authenticates, it stamps out Virtual-Access2. Same template, two live interfaces, each with its own tunnel destination filled in from the IKEv2 negotiation.
ip unnumbered Loopback0 means every Virtual-Access borrows the hub's loopback address (10.255.0.1), which is why later on you will see both of them showing the same IP. ip nhrp network-id and ip nhrp redirect are the groundwork for spoke-to-spoke shortcut switching, which is a topic of its own.
The gotcha that will eat your afternoon
The Virtual-Template must carry tunnel source and tunnel mode ipsec ipv4. If it does not, here is what happens: IKEv2 negotiates successfully, the peer authenticates, the Virtual-Access is cloned, it comes up, and then it immediately dies. Then it does it again. Forever. This is exactly what our lab did:
%LINK-3-UPDOWN: Interface Virtual-Access1, changed state to down
%DMVPN-5-NHRP_NETID_UNCONFIGURED: Virtual-Access1: NETID : 1 Unconfigured (Tunnel: 0.0.0.0 NBMA: UNKNOWN)Look closely at that second line. Tunnel: 0.0.0.0 and NBMA: UNKNOWN. The cloned interface has no idea what its own source address is or what encapsulation it should be using, so NHRP cannot register a network ID against it and the interface is torn back down.
What makes this genuinely nasty is that the crypto is fine. IKEv2 authentication succeeds. You can watch the authorization request go out in the debug:
IKEv2:Using mlist PLZ-FLEX-LIST and username PLZ-AUTHOR-POL for group author requestSo every instinct you have says crypto bug. You go and stare at your keyring, your proposal, your transform set, your identities. All of them are correct. The problem is two missing lines on an interface that is not even in your running config in the form you are looking at, because the thing that is flapping is a clone.
Add tunnel source GigabitEthernet2 and tunnel mode ipsec ipv4 to the Virtual-Template. The flapping stops instantly. Also note the message is tagged %DMVPN, not %FLEXVPN, because FlexVPN reuses the NHRP machinery that DMVPN is built on. Same plumbing, different front end.
Block 7: The Spoke
crypto ikev2 authorization policy PLZ-AUTHOR-POL
route set interface
route set access-list PLZ-FLEX-ROUTES
crypto ikev2 profile PLZ-FLEX-PROF
match identity remote fqdn domain pinglabz.lab
identity local fqdn spoke1.pinglabz.lab
authentication local pre-share
authentication remote pre-share
keyring local PLZ-FLEX-KEYRING
aaa authorization group psk list PLZ-FLEX-LIST PLZ-AUTHOR-POL
virtual-template 1
interface Tunnel0
ip address negotiated
ip nhrp network-id 1
ip nhrp shortcut virtual-template 1
tunnel source GigabitEthernet2
tunnel mode ipsec ipv4
tunnel destination 203.0.113.1
tunnel protection ipsec profile PLZ-FLEX-IPSECThe spoke is a mirror image with three differences that matter.
ip address negotiated. The spoke does not configure a tunnel IP. It asks for one, and the hub's PLZ-FLEX-POOL gives it one. This is the client relationship made concrete.
A static tunnel destination. The hub is dynamic and accepts anyone; the spoke knows exactly where it is going, so it points a normal Tunnel interface at 203.0.113.1. No Virtual-Template needed for the hub-facing tunnel (the spoke's virtual-template 1 reference exists for the spoke-to-spoke case).
Its own identity and its own route-set ACL. identity local fqdn spoke1.pinglabz.lab must land inside the hub's fqdn domain pinglabz.lab match, and SPOKE1's PLZ-FLEX-ROUTES ACL permits 10.30.10.0 0.0.0.255, which is how the hub learns SPOKE1's LAN. SPOKE2 is identical apart from spoke2.pinglabz.lab and 10.30.20.0 0.0.0.255. Route advertisement runs both directions over the same IKEv2 exchange.
Verification: Does It Actually Work?
Two spokes, both READY, both AES-GCM-256 with DH group 19 (note Hash: None, which is correct for an AEAD cipher because GCM handles integrity internally):
HUB#show crypto ikev2 sa
Tunnel-id Local Remote fvrf/ivrf Status
1 203.0.113.1/500 198.51.100.2/500 none/none READY
Encr: AES-GCM, keysize: 256, PRF: SHA256, Hash: None, DH Grp:19, Auth sign: PSK, Auth verify: PSK
Tunnel-id Local Remote fvrf/ivrf Status
2 203.0.113.1/500 198.51.100.6/500 none/none READY
Encr: AES-GCM, keysize: 256, PRF: SHA256, Hash: None, DH Grp:19, Auth sign: PSK, Auth verify: PSKTwo Virtual-Access interfaces exist that you never typed into the config, both up, both wearing the hub's Loopback0 address:
HUB#show ip interface brief | include Virtual-Access
Virtual-Access1 10.255.0.1 YES unset up up
Virtual-Access2 10.255.0.1 YES unset up up
HUB#show crypto session
Interface: Virtual-Access1
Session status: UP-ACTIVE
Peer: 198.51.100.2 port 500
Interface: Virtual-Access2
Session status: UP-ACTIVE
Peer: 198.51.100.6 port 500Now the payoff. Look at the hub's routing table:
HUB#show ip route | include Virtual-Access
S 10.0.9.24/32 is directly connected, Virtual-Access1
S 10.0.9.25/32 is directly connected, Virtual-Access2
S 10.30.10.0/24 is directly connected, Virtual-Access1
S 10.30.20.0/24 is directly connected, Virtual-Access2SPOKE1's LAN and SPOKE2's LAN, each pointing at the correct Virtual-Access, plus the two /32s for the pool addresses the spokes were handed. There is no OSPF here. No EIGRP, no BGP, no static route you typed. IKEv2 installed all four entries out of the authorization policy.
The pool confirms the allocation:
HUB#show ip local pool PLZ-FLEX-POOL
Pool Begin End Free In use
PLZ-FLEX-POOL 10.0.9.10 10.0.9.100 89 2
Inuse addresses:
10.0.9.24 IKEv2 Addr IDB
10.0.9.25 IKEv2 Addr IDBIKEv2 Addr IDB is the pool telling you who took the address and why. Two spokes, two leases, 89 free.
And the same trick ran in reverse. On the spoke:
SPOKE1#show ip route static
S* 0.0.0.0/0 [1/0] via 198.51.100.1
S 10.20.10.0/24 is directly connected, Tunnel0
S 10.30.0.0/16 is directly connected, Tunnel0
S 10.255.0.1/32 is directly connected, Tunnel0
S 192.168.99.0/24 is directly connected, Tunnel0SPOKE1 started with one static default route to its ISP. It now also has the hub's LAN (10.20.10.0/24), the hub's real-VM segment (192.168.99.0/24), the hub's tunnel endpoint (10.255.0.1/32 from route set interface), and the 10.30.0.0/16 summary that lets it reach the other spoke. All four arrived over IKEv2.
End to End: Spoke to Spoke
The proof that the route injection is not just cosmetic: SPOKE1 pinging SPOKE2's LAN, sourced from its own LAN interface.
SPOKE1#traceroute 10.30.20.1 source Loopback10 probe 1 timeout 1
Type escape sequence to abort.
Tracing the route to 10.30.20.1
1 10.255.0.1 34 msec
2 10.0.9.25 81 msec
SPOKE1#ping 10.30.20.1 source Loopback10 repeat 20
!!!!!!!!!!!!!!!!!!!!
Success rate is 100 percent (20/20), round-trip min/avg/max = 4/7/15 msTwenty for twenty. Two hops: the hub's tunnel address (10.255.0.1), then SPOKE2's pool address (10.0.9.25). That is spoke-to-spoke traffic hairpinning through the hub, decrypted on arrival and re-encrypted on the way back out. It works, and for a lot of designs that is entirely acceptable. Turning those two hops into one is the job of NHRP shortcut switching, which gets its own article.
Troubleshooting: What Actually Broke
%DMVPN-5-NHRP_NETID_UNCONFIGURED with Tunnel: 0.0.0.0 NBMA: UNKNOWN, in a loop. IKEv2 auth succeeds.tunnel source and tunnel mode ipsec ipv4 to the Virtual-Template.identity local fqdn must end in the domain the hub matches. spoke1.pinglabz.lab matches fqdn domain pinglabz.lab. A typo in either half is a silent no-match.IKEv2:Using mlist PLZ-FLEX-LIST and username PLZ-AUTHOR-POL for group author request. If it is missing, check aaa new-model, the aaa authorization network list, and the aaa authorization group psk list line in the IKEv2 profile.One more habit worth building: when a FlexVPN Virtual-Access misbehaves, do not read the Virtual-Template to see what the interface looks like. Read the clone itself with show derived-config interface Virtual-Access1. That shows you the merged result of the template plus everything IKEv2 filled in, which is the config the router is actually running.
Key Takeaways
- The IKEv2 authorization policy is FlexVPN.
aaa new-model,aaa authorization network, andcrypto ikev2 authorization policywithpool,route set interface, androute set access-listare what push addresses and routes to the spokes. Everything else is supporting cast. - Routes arrive over IKEv2, not over a routing protocol. Our hub learned 10.30.10.0/24 and 10.30.20.0/24, and the spokes learned 10.20.10.0/24 and 192.168.99.0/24, with zero OSPF, EIGRP, or BGP anywhere in the config.
- A Virtual-Template without
tunnel sourceandtunnel mode ipsec ipv4will flap forever and lie to you about it, because IKEv2 authentication succeeds while the cloned Virtual-Access dies in a loop. TheNBMA: UNKNOWNin the NHRP log is the tell. - A wildcard PSK (
address 0.0.0.0 0.0.0.0) is what makes the hub spoke-agnostic, and it is a real security tradeoff. Certificates are the production answer. - The hub scales without edits. One Virtual-Template, one FQDN domain match, one pool. Spoke number 47 needs zero changes on the hub.
- Spoke-to-spoke works, at two hops. 20/20 pings through the hub. Collapsing that to one hop is NHRP's job, not IKEv2's.
Next in this cluster: FlexVPN spoke-to-spoke and NHRP shortcut switching, where the two-hop path is supposed to become one. If you want the conceptual model behind everything above, read FlexVPN explained and IKEv2 explained, and for the wider picture of where FlexVPN sits next to crypto maps, SVTIs, and DMVPN, the IPsec VPN pillar is the map.