Routing protocols answer one question: what is the best path to this destination? Policy-based routing (PBR) exists for every time that is the wrong question. Send this application out the backup circuit. Keep guest traffic off the MPLS link. Steer one customer's flows through the firewall stack. PBR lets you route on source address, protocol, port, or packet size instead of destination alone, overriding the routing table for traffic you select. This post configures PBR on Cisco IOS XE, proves it with debug ip policy and route-map counters from a live lab, and covers the failure modes. It is part of the IP Routing cluster.
How PBR Fits Into the Forwarding Path
PBR is evaluated on ingress, before the destination lookup that would normally decide the packet's fate. The interface-level command ip policy route-map subjects every packet arriving on that interface to a route-map. Packets that match a permit entry get the route-map's set action (a next hop or exit interface); packets that match a deny entry, or match nothing, fall through to normal destination-based routing. Locally generated traffic is exempt unless you separately enable ip local policy route-map.
On IOS XE this is CEF-switched, not process-switched, so the old performance folklore about PBR melting routers no longer applies at these scales. The debug output later in this post shows the "FIB policy" path doing the work.
The Lab Scenario
R1 has two links to R2: a primary (10.0.12.0/24) and a deliberately expensive backup (10.0.112.0/24, OSPF cost 100). The routing table therefore always chooses the primary. Our policy: traffic sourced from the lab VM at 192.168.99.100 must use the long path. This is the classic "separate this one source from everyone else" pattern, the same shape as steering guest VLANs or backup jobs.
Configuration: Three Building Blocks
An ACL to describe the interesting traffic, a route-map to bind match to action, and the interface command to arm it:
R1(config)# ip access-list extended VM-SOURCE
R1(config-ext-nacl)# permit ip host 192.168.99.100 any
R1(config-ext-nacl)# exit
R1(config)# route-map PBR-VM permit 10
R1(config-route-map)# match ip address VM-SOURCE
R1(config-route-map)# set ip next-hop 10.0.112.2
R1(config-route-map)# exit
R1(config)# interface Ethernet0/0
R1(config-if)# ip policy route-map PBR-VMTwo design notes. First, the ACL permits, it does not filter: a permit in a PBR ACL means "this packet is subject to the policy", nothing is dropped by it. Second, set ip next-hop expects a directly reachable adjacency; the router ARPs for it on the connected subnet. For next hops that might go away, prefer set ip next-hop verify-availability with an IP SLA tracker, or the packet blackholes while the interface stays up.
The Set Commands, and When Each Applies
Proof 1: The Path Actually Changes
From the VM, before PBR, the traceroute follows the routing table across the primary link (second hop 10.0.12.2):
j@llmbits:~$ traceroute -n -q 1 -w 1 4.4.4.4
1 192.168.99.1 3.344 ms
2 10.0.12.2 4.373 ms
3 10.0.23.3 5.776 ms
4 10.0.34.4 7.364 msAfter applying the policy, same command, and the second hop flips to the long link:
j@llmbits:~$ traceroute -n -q 1 -w 1 4.4.4.4
1 192.168.99.1 5.429 ms
2 10.0.112.2 5.323 ms
3 10.0.23.3 5.855 ms
4 10.0.34.4 6.753 msThe routing table on R1 still prefers 10.0.12.2 for this destination the whole time. Only policy-selected traffic moved.
Proof 2: debug ip policy Shows the Decision
With debug ip policy running while the VM sends traffic (use this carefully in production; on a busy interface, rate-limit with an ACL-scoped debug), each packet logs the three-step verdict:
*Jul 11 16:36:53.241: IP: s=192.168.99.100 (Ethernet0/0), d=4.4.4.4, len 60, FIB policy match
*Jul 11 16:36:53.241: IP: s=192.168.99.100 (Ethernet0/0), d=4.4.4.4, len 60, PBR Counted
*Jul 11 16:36:53.241: IP: s=192.168.99.100 (Ethernet0/0), d=4.4.4.4, g=10.0.112.2, len 60, FIB policy routed
*Jul 11 16:36:53.242: IP: route map PBR-VM, item 10, permitRead the last line as the audit trail: route-map name, sequence number, and result. Traffic that does NOT match logs "policy rejected -- normal forwarding", which is your clue when a match clause is too narrow.
Proof 3: Counters for the Long Haul
Debugs are for the moment; counters are for the change ticket. show route-map accumulates matches per sequence:
R1# show route-map PBR-VM
route-map PBR-VM, permit, sequence 10
Match clauses:
ip address (access-lists): VM-SOURCE
Set clauses:
ip next-hop 10.0.112.2
Policy routing matches: 22 packets, 1700 bytesA policy with zero matches after a soak period means your ACL never fires (wrong source, wrong direction, wrong interface). A policy with matches but no path change means the set clause is failing (next hop unreachable), which show ip policy plus a ping of the next hop will confirm in seconds.
Where PBR Goes Wrong
Most PBR incidents trace to one of a handful of causes. The policy is applied to the wrong interface (it must be the INGRESS interface of the interesting traffic, not the egress you are steering toward). The next hop is not on a connected subnet, so the set clause silently never resolves. Return traffic was forgotten: PBR is unidirectional, and asymmetric paths through stateful devices (firewalls, NAT) drop the reply leg. Locally generated router traffic was expected to obey the policy but needs ip local policy. And policies without verify-availability keep forwarding into a dead next hop because the policy never consults the routing table's view of liveness.
There is also an operational cost worth naming: PBR is invisible to your routing protocols. A traceroute behaves unexpectedly and nothing in show ip route explains why. Document policies aggressively, keep the route-map names descriptive (PBR-VM beats RM-101), and audit with show ip policy, which lists interface-to-route-map bindings in one screen.
Surviving a Dead Next Hop: verify-availability Done Properly
The naked set ip next-hop keeps forwarding as long as the ARP entry resolves, which on an Ethernet segment can outlive the actual path by a long time (the next hop's switchport is up, the router behind it is not). The production-grade pattern ties the policy to an IP SLA probe:
ip sla 10
icmp-echo 10.0.112.2
frequency 5
ip sla schedule 10 life forever start-time now
!
track 10 ip sla 10 reachability
!
route-map PBR-VM permit 10
match ip address VM-SOURCE
set ip next-hop verify-availability 10.0.112.2 1 track 10When the track object goes down, the set clause is skipped and matching traffic falls through to normal routing, which is almost always the failure behavior you actually want: policy when possible, reachability always. The trailing 1 is a sequence number; you can chain multiple tracked next hops as an ordered failover list.
Policy for the Router's Own Traffic
ip policy route-map governs transit traffic only. Packets the router itself originates (pings from the CLI, syslog, SNMP traps, tunnel keepalives) consult the routing table directly unless you separately apply ip local policy route-map <name> in global configuration. This is both a gotcha and a tool: a local policy is the cleanest way to source management traffic out a dedicated path without touching the routing table, and forgetting the distinction is why "PBR works but my router's own pings ignore it" is a perennial forum thread. Test transit behavior from a host behind the router, not from the router's own CLI, or you are testing the wrong code path.
PBR or Something Else? Choosing the Right Tool
PBR
Per-source or per-application path selection at a specific ingress point. Surgical, unidirectional, invisible to protocols.
IGP metric tuning
Moves ALL traffic between paths, bidirectionally, with protocol-native failover. Prefer it when the answer is "everything should use the other link".
VRF segmentation
When "this traffic must use that path" is really "these users live in a different network", VRF-Lite gives whole routing tables instead of per-flow exceptions.
SD-WAN policies
Application-aware routing with liveness built in is PBR's spiritual descendant; if you are writing dozens of PBR entries for app steering, you are reinventing SD-WAN.
FAQ
Does PBR override static routes and routing protocols?
Yes. Policy evaluation happens before the destination lookup, so a matching permit entry wins over any routing table state, including statics and defaults. Deny entries and non-matches fall through to the table.
Can I match on port numbers or DSCP?
Anything an extended ACL expresses is matchable: ports, protocols, DSCP, packet length ranges (match length). Match on stable identifiers where possible; port-based application steering ages badly.
Is PBR bad for performance on IOS XE?
No. It is CEF-switched (the "FIB policy routed" lines in the debug are that path). The real costs are operational: an invisible forwarding exception that documentation and monitoring must carry.
Key Takeaways
PBR overrides destination routing for traffic you select by source, port, protocol, or size, evaluated at ingress before the FIB lookup. Build it as ACL, route-map, and ip policy route-map on the ingress interface, and prefer verify-availability whenever the next hop can fail independently of the link. Verify in three layers: traceroute for the path, debug ip policy for per-packet decisions, and route-map counters for sustained proof. And remember it is one-directional, so design the return path deliberately. For how policy interacts with the underlying table, the redistribution guide and the rest of the IP Routing pillar cover the destination-based machinery PBR sits on top of.