IPv4 addresses ran out at the IANA level back in 2011. Regional registries have been in conservation mode for years. Yet walk into most enterprise networks today and you’ll find a stubborn holdout: dual-stack deployments that treat IPv6 as a checkbox rather than a first-class citizen, or worse, networks still running IPv4-only with no migration plan in sight.
That’s changing fast. ISPs are pushing IPv6 hard, cloud providers increasingly prefer it, and modern Cisco hardware has supported it for years — you just need to know how to configure it properly on IOS-XE. This guide walks through a real-world IPv6 deployment: addressing, routing with OSPFv3, dual-stack operation, access control, and the verification commands you’ll need to confirm everything is working.
IPv6 Addressing Refresher Before We Touch the CLI
IPv6 addresses are 128 bits, written as eight groups of four hex digits separated by colons. Two rules simplify notation: leading zeros in a group can be omitted, and one contiguous sequence of all-zero groups can be collapsed to ::.
The address types you’ll encounter most in enterprise IOS-XE deployments:
- Global unicast (2000::/3) — the IPv6 equivalent of public IPv4. Routable on the internet.
- Link-local (fe80::/10) — auto-configured on every IPv6-enabled interface. Not routable beyond a single L2 segment, but essential for neighbor discovery and routing protocol adjacencies.
- Unique local (fc00::/7) — the RFC 4193 equivalent of RFC 1918 private space. Routable within an organization but not on the public internet.
- Multicast (ff00::/8) — replaces IPv4 broadcast for most purposes. Neighbor solicitation, router advertisement, and OSPFv3 all use specific multicast groups.
For subnetting, the convention is simple: ISPs typically hand you a /48, you carve it into /64 subnets (one per VLAN or segment), and the last 64 bits are the interface identifier. A /64 gives you 18.4 quintillion addresses per subnet — you will never run out on a single segment.
If you’re still getting comfortable with Cisco OS variants and their feature parity, our IOS vs IOS-XE vs IOS-XR breakdown covers what each platform supports for modern protocols including IPv6.
Lab Topology
We’re using three routers to demonstrate the key scenarios:
- R1 — edge/upstream router, simulating ISP handoff
- R2 — core distribution router
- R3 — access/branch router
Prefix block in use: 2001:db8:acad::/48 (documentation prefix per RFC 3849 — substitute your real allocation in production).
Step 1: Enable IPv6 Unicast Routing
IPv6 packet forwarding is disabled by default on IOS-XE. This single global command enables it:
R1(config)# ipv6 unicast-routing
Without this, the router will process IPv6 for its own interfaces but won’t forward packets between them. Apply it on every router in your topology.
Step 2: Configure Interface Addresses
You have three ways to assign an IPv6 global unicast address to an interface on IOS-XE.
Method 1: Static assignment
R1(config)# interface GigabitEthernet0/0
R1(config-if)# ipv6 address 2001:db8:acad:1::1/64
R1(config-if)# no shutdown
Method 2: EUI-64 (interface ID derived from MAC address)
R1(config)# interface GigabitEthernet0/1
R1(config-if)# ipv6 address 2001:db8:acad:2::/64 eui-64
R1(config-if)# no shutdown
EUI-64 takes the 48-bit MAC address, inserts ff:fe in the middle, and flips the universal/local bit to produce a 64-bit interface identifier. It works well for infrastructure interfaces where the address doesn’t need to be memorable, but avoid it for servers and devices that need consistent addressing.
Method 3: Link-local only (explicit)
Link-local addresses are auto-generated as soon as you enable IPv6 on an interface, but you can override with a predictable one:
R1(config)# interface GigabitEthernet0/2
R1(config-if)# ipv6 address fe80::1 link-local
R1(config-if)# ipv6 enable
Using fe80::1, fe80::2 etc. on your router interfaces makes troubleshooting dramatically easier — you can immediately tell which router you’re talking to from a ping or traceroute output.
Verifying interface addressing
R1# show ipv6 interface GigabitEthernet0/0
GigabitEthernet0/0 is up, line protocol is up
IPv6 is enabled, link-local address is FE80::1
No Virtual link-local address(es):
Global unicast address(es):
2001:DB8:ACAD:1::1, subnet is 2001:DB8:ACAD:1::/64
Joined group address(es):
FF02::1
FF02::2
FF02::1:FF00:1
MTU is 1500 bytes
ICMP error messages limited to one every 100 milliseconds
ICMP redirects are enabled
ICMP unreachables are sent
ND DAD is enabled, number of DAD attempts: 1
ND reachable time is 30000 milliseconds (using 30000)
ND NS retransmit interval is 1000 milliseconds
Note the multicast groups: FF02::1 is all-nodes, FF02::2 is all-routers, and FF02::1:FF00:1 is the solicited-node multicast for this specific address — used for Neighbor Discovery Protocol (NDP) address resolution, which replaces ARP in IPv6.
Step 3: OSPFv3 for IPv6 Routing
OSPFv3 is the IPv6-native version of OSPF. On modern IOS-XE (15.x+), the preferred approach is the address-family model, which lets a single OSPFv3 process carry both IPv4 and IPv6 topologies. This is cleaner than running separate processes.
R1(config)# router ospfv3 1
R1(config-router)# router-id 1.1.1.1
R1(config-router)# address-family ipv6 unicast
R1(config-router-af)# exit-address-family
Then enable it per-interface:
R1(config)# interface GigabitEthernet0/0
R1(config-if)# ospfv3 1 ipv6 area 0
R1(config)# interface GigabitEthernet0/1
R1(config-if)# ospfv3 1 ipv6 area 0
OSPFv3 uses link-local addresses for adjacency formation — which is why those predictable fe80::1 addresses matter. The hello/dead timers, authentication, and area types all work identically to OSPFv2. If you’ve been troubleshooting OSPFv2 adjacency failures, our OSPF troubleshooting guide applies directly to OSPFv3 adjacency problems as well.
Verifying OSPFv3 operation
R1# show ospfv3 neighbor
OSPFv3 1 address-family ipv6 (router-id 1.1.1.1)
Neighbor ID Pri State Dead Time Interface ID Interface
2.2.2.2 1 FULL/DR 00:00:37 5 GigabitEthernet0/0
3.3.3.3 1 FULL/BDR 00:00:36 5 GigabitEthernet0/0
R1# show ipv6 route ospf
IPv6 Routing Table - default - 8 entries
Codes: C - Connected, L - Local, S - Static, U - Per-user Static route
B - BGP, HA - Home Agent, MR - Mobile Router, R - RIP
H - NHRP, I1 - ISIS L1, I2 - ISIS L2, IA - ISIS interarea
IS - ISIS summary, D - EIGRP, EX - EIGRP external, NM - NEMO
ND - ND Default, NDp - ND Prefix, DCE - Destination, NDr - Redirect
RL - RPL, O - OSPF Intra, OI - OSPF Inter, OE1 - OSPF ext 1
OE2 - OSPF ext 2, ON1 - OSPF NSSA ext 1, ON2 - OSPF NSSA ext 2
la - LISP site, lr - LISP site-registrations, ld - LISP site-decap
O 2001:DB8:ACAD:2::/64 [110/2]
via FE80::2, GigabitEthernet0/0
O 2001:DB8:ACAD:3::/64 [110/3]
via FE80::2, GigabitEthernet0/0
Step 4: Dual-Stack Configuration
In practice, you’ll run dual-stack during the transition period — IPv4 and IPv6 simultaneously on the same interfaces. IOS-XE handles this natively; there’s no special dual-stack mode to enable. Just configure both address families on each interface:
R2(config)# interface GigabitEthernet0/0
R2(config-if)# ip address 10.1.12.2 255.255.255.0
R2(config-if)# ipv6 address 2001:db8:acad:1::2/64
R2(config-if)# ipv6 address fe80::2 link-local
R2(config-if)# no shutdown
Run OSPFv2 for your existing IPv4 topology and OSPFv3 for IPv6 — they operate completely independently. The address-family model in OSPFv3 can also carry IPv4 if you want to consolidate, but most shops keep them separate during migration for operational clarity.
For BGP dual-stack, you activate both address families under the same neighbor:
R1(config)# router bgp 65001
R1(config-router)# neighbor 2001:db8:acad:1::2 remote-as 65002
R1(config-router)# address-family ipv4
R1(config-router-af)# no neighbor 2001:db8:acad:1::2 activate
R1(config-router-af)# exit-address-family
R1(config-router)# address-family ipv6
R1(config-router-af)# neighbor 2001:db8:acad:1::2 activate
R1(config-router-af)# exit-address-family
Step 5: IPv6 Access Control Lists
IPv6 ACLs on IOS-XE are named only (no numbered ACLs), and the syntax differs slightly from IPv4. A critical difference: IPv6 ACLs automatically permit ICMPv6 neighbor discovery traffic at the end — neighbor solicitation and advertisement messages need to flow for NDP to function. If you write an explicit deny-all without accounting for this, you’ll break IPv6 connectivity even if the ACL logic looks correct.
R1(config)# ipv6 access-list BLOCK_EXTERNAL_V6
R1(config-ipv6-acl)# permit tcp 2001:db8:acad::/48 any established
R1(config-ipv6-acl)# permit icmp any any nd-na
R1(config-ipv6-acl)# permit icmp any any nd-ns
R1(config-ipv6-acl)# permit icmp 2001:db8:acad::/48 any
R1(config-ipv6-acl)# deny ipv6 any any log
R1(config)# interface GigabitEthernet0/2
R1(config-if)# ipv6 traffic-filter BLOCK_EXTERNAL_V6 in
Verify the ACL is matching traffic:
R1# show ipv6 access-list BLOCK_EXTERNAL_V6
IPv6 access list BLOCK_EXTERNAL_V6
permit tcp 2001:DB8:ACAD::/48 any established sequence 10 (245 matches)
permit icmp any any nd-na sequence 20 (12 matches)
permit icmp any any nd-ns sequence 30 (18 matches)
permit icmp 2001:DB8:ACAD::/48 any sequence 40 (87 matches)
deny ipv6 any any log sequence 50 (3 matches)
For tighter management-plane security, combine IPv6 ACLs with Control Plane Policing — see our CoPP configuration guide for the full framework including IPv6 class-maps.
Step 6: Neighbor Discovery Protocol (NDP) Tuning
NDP is IPv6’s replacement for ARP, ICMP Router Discovery, and ICMP Redirect combined. It uses ICMPv6 messages over link-local multicast. Understanding NDP is essential for troubleshooting reachability issues that have no IPv4 equivalent.
The neighbor table is equivalent to the ARP cache:
R1# show ipv6 neighbors
IPv6 Address Age Link-layer Addr State Interface
FE80::2 0 5254.0002.0001 REACH Gi0/0
2001:DB8:ACAD:1::2 2 5254.0002.0001 STALE Gi0/0
FE80::3 1 5254.0003.0001 REACH Gi0/0
States to know: REACH (confirmed reachable within the last NDP reachable time), STALE (reachable time expired but no traffic to trigger re-verification), DELAY (waiting for upper-layer confirmation), PROBE (actively sending neighbor solicitations to re-verify). If you see entries stuck in PROBE, that’s your NDP equivalent of an ARP failure — usually a cable, VLAN, or ACL issue.
Useful NDP debugging command when adjacency won’t form:
R1# debug ipv6 nd
*Jun 12 09:14:22.731: ICMPv6-ND: Received RA from FE80::2 on GigabitEthernet0/0
*Jun 12 09:14:22.732: ICMPv6-ND: Sending NA for 2001:DB8:ACAD:1::1 to FF02::1 on GigabitEthernet0/0
Turn it off after: no debug ipv6 nd. Don’t leave IPv6 ND debugging on in production — on a busy interface it generates significant CPU load.
Common Gotchas and Production Notes
SLAAC vs. DHCPv6 Stateful: Pick the Right Host Assignment Model
When it comes to assigning IPv6 addresses to end hosts, you have three options — and getting this wrong causes headaches at scale.
SLAAC (Stateless Address Autoconfiguration) is the default: the host generates its own address from the /64 prefix announced in Router Advertisements and a self-derived interface ID. Zero configuration required on the router beyond enabling IPv6 on the interface. The downside: no central record of which host took which address.
DHCPv6 Stateless combines SLAAC for addressing with DHCPv6 for options (DNS servers, domain search lists). Set the M flag to 0 and the O flag to 1 in the RA:
R1(config)# interface GigabitEthernet0/1
R1(config-if)# ipv6 nd other-config-flag
DHCPv6 Stateful gives you full address assignment control — analogous to DHCPv4. Set the M flag to 1:
R1(config)# ipv6 dhcp pool LAN_POOL
R1(config-dhcpv6)# address prefix 2001:db8:acad:1::/64 lifetime 86400 3600
R1(config-dhcpv6)# dns-server 2001:db8:acad::53
R1(config)# interface GigabitEthernet0/1
R1(config-if)# ipv6 dhcp server LAN_POOL
R1(config-if)# ipv6 nd managed-config-flag
Prefix delegation from your ISP
If your ISP provides a dynamic prefix via DHCPv6 Prefix Delegation (PD), configure the WAN interface as a requesting router. No local pool definition is needed on the CPE — just enable PD on the upstream interface and then reference the received prefix on LAN interfaces:
R1(config)# interface GigabitEthernet0/0
R1(config-if)# ipv6 dhcp client pd MY_PREFIX
R1(config-if)# ipv6 address autoconfig
Then reference the delegated prefix on a LAN interface:
R1(config)# interface GigabitEthernet0/1
R1(config-if)# ipv6 address MY_PREFIX 0:0:0:1::1/64
Router Advertisement suppression on transit links
On point-to-point links between routers, suppress RAs to prevent spurious default route injection:
R1(config)# interface GigabitEthernet0/0
R1(config-if)# ipv6 nd ra suppress all
The /127 debate on point-to-point links
RFC 6164 recommends /127 prefixes for router-to-router links to eliminate the subnet-router anycast address and reduce attack surface. IOS-XE supports /127 natively — no special configuration needed, just set the prefix length:
R1(config)# interface GigabitEthernet0/0
R1(config-if)# ipv6 address 2001:db8:acad:ff::0/127
The peer would use 2001:db8:acad:ff::1/127.
IPv6 and HSRP/GLBP
HSRP version 2 supports IPv6. Configuration is similar to v4 but uses a link-local virtual address:
R1(config)# interface GigabitEthernet0/1
R1(config-if)# standby version 2
R1(config-if)# standby 10 ipv6 autoconfig
R1(config-if)# standby 10 priority 110
R1(config-if)# standby 10 preempt
Full Verification Checklist
Before calling an IPv6 deployment done, run through this sequence:
# Check IPv6 is forwarding
R1# show ipv6 interface brief
GigabitEthernet0/0 [up/up]
FE80::1
2001:DB8:ACAD:1::1
GigabitEthernet0/1 [up/up]
FE80::1
2001:DB8:ACAD:2::1
# Verify routing table has expected prefixes
R1# show ipv6 route
# Check OSPFv3 neighbors are FULL
R1# show ospfv3 neighbor
# Verify NDP entries for directly connected hosts
R1# show ipv6 neighbors
# Test end-to-end reachability
R1# ping ipv6 2001:db8:acad:3::1 source 2001:db8:acad:1::1
# Traceroute for path verification
R1# traceroute ipv6 2001:db8:acad:3::1
If ping fails but the route exists and NDP shows the neighbor as REACH, check for IPv6 ACLs on the path — show ipv6 access-list on each hop to see if traffic is being matched and dropped.
What’s Next
This covers the core deployment: addressing, OSPFv3 routing, dual-stack operation, ACLs, and NDP. From here, the next layers are IPv6-aware security policies (RA Guard, DHCPv6 Guard, IPv6 Source Guard on access switches), Segment Routing over IPv6 (SRv6) for service provider deployments, and integrating IPv6 telemetry into your monitoring stack — LibreNMS and Grafana both have full IPv6 support and can poll IOS-XE devices over IPv6 transport directly.
The investment in getting IPv6 right now pays dividends as more cloud workloads default to it and legacy IPv4 NAT architectures become operational liabilities. The CLI syntax is different enough from IPv4 to trip you up the first few times, but the concepts are the same — addresses, routing, access control. Start with a lab topology, get OSPFv3 adjacencies up, verify the routing table looks right, then replicate the pattern to production.