IP Services · · 10 min read

Flexible NetFlow on Cisco IOS XE: Records, Monitors, and Exporters

Flexible NetFlow on Cisco IOS XE: nfdump decoding 14 real flows exported from the router

NetFlow answers the question your monitoring dashboard cannot: not "is the link busy" but "who is making it busy, talking to what, on which port, and how much." It is the difference between knowing you have a problem and knowing what the problem is.

Flexible NetFlow (FNF) is the modern implementation, and it is genuinely flexible: instead of a fixed set of exported fields, you define your own record. That flexibility is also why it confuses people, because you now have three objects to configure instead of one. This article builds the whole thing on Cisco IOS XE, exports the flows to a real Linux collector, and decodes them there, so you can see the data arrive rather than take it on faith. It extends the IP Services cluster guide.

The Three Objects (and Why There Are Three)

Every Flexible NetFlow configuration is built from three pieces that plug into each other. Get the mental model right and the syntax stops being arbitrary.

1. Flow Record
Answers
WHAT do I care about? Which fields define a flow, and which do I count?
Keywordsmatch / collect
2. Flow Exporter
Answers
WHERE do I send it? Collector IP, port, protocol version.
OptionalYes (cache-only is valid)
3. Flow Monitor
Answers
Glues record + exporter together and owns the cache. This is what you apply to an interface.
Applied toInterfaces

The monitor is the only one that touches an interface. The record and exporter are reusable building blocks: one record can feed many monitors, one exporter can serve many monitors.

match vs collect: The Distinction That Matters

This trips up almost everyone the first time. Inside a flow record:

  • match defines a KEY field. Key fields are the flow's identity. If two packets have identical values across every key field, they belong to the same flow. Change one key field and it is a different flow, with its own cache entry.
  • collect defines a NON-KEY field. Non-key fields are the things you accumulate or record about the flow: byte counts, packet counts, timestamps, which interface it came in on.

Choosing keys is a real design decision. Match on the classic five-tuple (source IP, destination IP, protocol, source port, destination port) and you get per-conversation visibility. Match only on source and destination IP and you get a much smaller cache but you lose the ability to say "it was all port 443." Match on too much and the cache explodes.

Here is the record from the lab, a standard five-tuple plus ToS:

R1(config)# flow record PINGLABZ-IPV4
R1(config-flow-record)#  match ipv4 source address
R1(config-flow-record)#  match ipv4 destination address
R1(config-flow-record)#  match ipv4 protocol
R1(config-flow-record)#  match transport source-port
R1(config-flow-record)#  match transport destination-port
R1(config-flow-record)#  match ipv4 tos
R1(config-flow-record)#  collect counter bytes long
R1(config-flow-record)#  collect counter packets long
R1(config-flow-record)#  collect timestamp absolute first
R1(config-flow-record)#  collect timestamp absolute last
R1(config-flow-record)#  collect interface input
R1(config-flow-record)#  collect interface output

Verify what the router made of it:

R1#show flow record PINGLABZ-IPV4
flow record PINGLABZ-IPV4:
  Description:        Key fields identify the flow; nonkey fields are what we count
  No. of users:       1
  Total field space:  54 bytes
  Fields:
    match ipv4 tos
    match ipv4 protocol
    match ipv4 source address
    match ipv4 destination address
    match transport source-port
    match transport destination-port
    collect interface input
    collect interface output
    collect counter bytes long
    collect counter packets long
    collect timestamp absolute first
    collect timestamp absolute last

Total field space: 54 bytes. That is the per-flow memory cost of your design decision, stated plainly. Add more key fields and this grows, and so does every exported record on the wire.

The Exporter: Getting Flows Off the Box

The cache on the router is useful for a quick look, but the point of NetFlow is shipping the data somewhere it can be stored and queried. In the lab the collector is a real Debian machine at 192.168.99.100 running nfcapd.

R1(config)# flow exporter PINGLABZ-COLLECTOR
R1(config-flow-exporter)#  destination 192.168.99.100
R1(config-flow-exporter)#  source Ethernet0/0
R1(config-flow-exporter)#  transport udp 2055
R1(config-flow-exporter)#  export-protocol netflow-v9
R1(config-flow-exporter)#  template data timeout 60

Two lines deserve attention. source Ethernet0/0 pins the source address of the export packets, which matters because collectors identify exporters by source IP; if it changes, your collector thinks a new device appeared. And template data timeout 60 controls how often the v9 template is resent, which is the single most important setting for a collector that might restart. More on why in the NetFlow versions comparison.

R1#show flow exporter PINGLABZ-COLLECTOR
Flow Exporter PINGLABZ-COLLECTOR:
  Description:              Export to the Debian VM running nfcapd
  Export protocol:          NetFlow Version 9
  Transport Configuration:
    Destination type:       IP
    Destination IP address: 192.168.99.100
    Source IP address:      192.168.99.1
    Source Interface:       Ethernet0/0
    Transport Protocol:     UDP
    Destination Port:       2055
    Source Port:            58769
    DSCP:                   0x0
    TTL:                    255
  Export template data timeout:        60

The Monitor: Cache and Application

R1(config)# flow monitor PINGLABZ-MON
R1(config-flow-monitor)#  record PINGLABZ-IPV4
R1(config-flow-monitor)#  exporter PINGLABZ-COLLECTOR
!
R1(config)# interface Ethernet0/2
R1(config-if)#  ip flow monitor PINGLABZ-MON input
R1(config-if)#  ip flow monitor PINGLABZ-MON output

Applying it input and output on the same interface is deliberate. NetFlow is directional: an input monitor sees traffic arriving on that interface only. If you apply it in one direction, you will see half of every conversation and spend an afternoon wondering why the byte counts look wrong.

R1#show flow monitor PINGLABZ-MON
Flow Monitor PINGLABZ-MON:
  Flow Record:       PINGLABZ-IPV4
  Flow Exporter:     PINGLABZ-COLLECTOR
  Cache:
    Type:                 normal
    Status:               allocated
    Size:                 4096 entries / 491584 bytes
    Inactive Timeout:     15 secs
    Active Timeout:       1800 secs

Those two timeouts govern when a flow leaves the cache and gets exported:

Inactive timeout (15s)The flow has gone quiet for this long, so it is finished. Age it out and export.
Active timeout (1800s)The flow is still running, but export a snapshot anyway so long-lived transfers are not invisible for 30 minutes.

A platform note, honestly stated: on the IOL-XE image used for this lab (17.18.2), the cache timeout active and cache timeout inactive commands are rejected under the flow monitor with % Invalid input detected. The defaults above are what you get. On real hardware and on CSR/Catalyst 8000v images those commands work normally, and tuning the active timeout down to 60 seconds is standard practice so long transfers show up in your dashboard while they are still happening. This article shows the default behaviour because that is what the lab genuinely produced.

Reading the Cache

With iperf3 and ping traffic pushed across the monitored interface, the cache fills up:

R1#show flow monitor PINGLABZ-MON cache format table
  Cache type:                               Normal
  Cache size:                                 4096
  Current entries:                               5
  High Watermark:                               12

  Flows added:                                  12
  Flows aged:                                    7
    - Active timeout      (  1800 secs)          0
    - Inactive timeout    (    15 secs)          7

IPV4 SRC ADDR    IPV4 DST ADDR    TRNS SRC PORT  TRNS DST PORT  IP PROT  intf input  intf output   bytes long  pkts long
===============  ===============  =============  =============  =======  ==========  ===========  ===========  =========
192.168.99.100   10.0.10.100              51236           5201        6  Et0/0       Et0/2               1225         14
10.0.10.100      192.168.99.100            5201          51236        6  Et0/2       Et0/0               1017         13
192.168.99.100   10.0.10.100              46438           5201       17  Et0/0       Et0/2            1019948        692
192.168.99.100   10.0.10.100                  0           2048        1  Et0/0       Et0/2                252          3
10.0.10.100      192.168.99.100               0              0        1  Et0/2       Et0/0                252          3

Every row is a flow, and the key fields are doing exactly what they promised. Protocol 6 is TCP (the iperf3 control connection, both directions, separately). Protocol 17 is UDP, and it is the big one at roughly a megabyte in 692 packets. Protocol 1 is ICMP, and note the port columns: ICMP has no ports, so the router stuffs the type and code in there instead, which is why you see 2048 (type 8, echo request, shifted).

Look at the Flows aged counter: 7 flows aged out via the inactive timeout, 0 via the active timeout. Those 7 are what got exported.

And confirm the export actually happened:

R1#show flow exporter PINGLABZ-COLLECTOR statistics
Flow Exporter PINGLABZ-COLLECTOR:
  Packet send statistics (last cleared 00:01:43 ago):
    Successfully sent:         2                     (594 bytes)

  Client send statistics:
    Client: Flow Monitor PINGLABZ-MON
      Records added:           10
        - sent:                7
      Bytes added:             540
        - sent:                378

If Successfully sent stays at 0, the router is not shipping anything and you have a routing or source-interface problem, not a NetFlow problem.

On the Wire

Do not trust the router's own counters alone. Here is tcpdump on the collector, confirming the export packets physically arrive:

j@llmbits:~$ sudo tcpdump -i ens224 -n udp port 2055
listening on ens224, link-type EN10MB (Ethernet), snapshot length 262144 bytes
18:44:51.294654 IP 192.168.99.1.58769 > 192.168.99.100.2055: UDP, length 296
18:44:52.295245 IP 192.168.99.1.58769 > 192.168.99.100.2055: UDP, length 132

Two packets, two different sizes, and that difference is the whole of NetFlow v9 in one screen. The 296-byte packet is the template: it tells the collector what the fields are and in what order. The 132-byte packet is the data: just values, no field names, because the collector already knows the layout from the template.

This is why template data timeout matters so much. If your collector restarts and misses the template, it cannot decode a single data packet until the next template arrives. Sixty seconds of blindness is tolerable. The default of 600 is not.

Decoded on the Collector

The final proof. nfcapd wrote the flows to disk; nfdump reads them back:

j@llmbits:~$ nfdump -R /home/j/nfcap -o long
Date first seen         Duration   Proto  Src IP Addr:Port        Dst IP Addr:Port      Packets    Bytes Flows
2026-07-11 17:45:30.137 00:00:02.004 ICMP  192.168.99.100:0    ->    10.0.10.100:8.0          3      252     1
2026-07-11 17:45:30.139 00:00:02.004 ICMP     10.0.10.100:0    -> 192.168.99.100:0.0          3      252     1
2026-07-11 17:46:38.021 00:00:05.073 TCP   192.168.99.100:40556 ->   10.0.10.100:5201        16     1488     1
2026-07-11 17:46:38.023 00:00:05.070 TCP      10.0.10.100:5201 -> 192.168.99.100:40556       14     1222     1
2026-07-11 17:46:38.031 00:00:05.058 TCP   192.168.99.100:40570 ->   10.0.10.100:5201      6467    9.7 M     1
2026-07-11 17:46:38.033 00:00:05.060 TCP      10.0.10.100:5201 -> 192.168.99.100:40570      3653   194636     1
2026-07-11 17:46:38.037 00:00:05.047 TCP   192.168.99.100:40578 ->   10.0.10.100:5201      5425    8.1 M     1
2026-07-11 17:46:43.111 00:00:05.002 UDP   192.168.99.100:51231 ->   10.0.10.100:5201      1728    2.5 M     1
2026-07-11 17:46:48.132 00:00:01.001 ICMP  192.168.99.100:0    ->    10.0.10.100:8.0          2      168     1
Summary: total flows: 14, total bytes: 20.7 M, total packets: 20412, avg bps: 2.1 M, avg pps: 258, avg bpp: 1016
Time window: 2026-07-11 17:45:30 - 2026-07-11 17:49:00, Duration: 00:03:30.000

Fourteen flows, 20.7 megabytes, 20,412 packets, and every one of them is a real packet the router forwarded and reported. The two parallel TCP streams at 9.7M and 8.1M are the iperf3 -P 2 test. The 2.5M UDP flow is the UDP test. The asymmetry between the TCP send and receive rows (9.7 M out, 194 KB back) is exactly what a bulk transfer looks like: data one way, ACKs the other.

That is the payoff. Not a config snippet, but the actual data landing somewhere you can query it.

Sampling, and Not Melting the Box

NetFlow costs CPU and memory. On a busy edge router, processing every single packet is not free, and a 4096-entry cache fills fast on a real internet link.

Two mitigations:

  • Sampling. A flow sampler tells the router to process 1 in N packets. Statistically valid for volume trends, useless for security forensics (you will miss the one packet that mattered). Sample on high-speed core links, never on a link you are using for incident response.
  • Cache sizing. cache entries N raises the ceiling. Watch the High Watermark in the cache output. If it is pinned at your cache size, you are dropping flows and your data is silently incomplete.

The general rule: monitor the interfaces where the answer lives (edge, WAN, DMZ), not every interface on every box. NetFlow on a core link between two aggregation switches usually tells you nothing you cannot get from an interface counter.

When It Does Not Work

Cache is emptyMonitor not applied, or applied to an interface with no traffic. Check show flow interface.
Cache fills, nothing exportedNo exporter bound to the monitor, or Successfully sent: 0. Check routing to the collector.
Packets sent, collector shows nothingAlmost always the template. Restart order matters; lower template data timeout.
Only half the conversationMonitor applied input only. Add output.

And when you need to see individual packets rather than flow summaries, that is a different tool: see SPAN, RSPAN, and ERSPAN for packet mirroring, and conditional debugging for surgical debug output.

FAQ

Do I need a flow exporter?

No. A monitor with only a record is valid and gives you show flow monitor cache on the box. That is genuinely useful for live troubleshooting. You need an exporter when you want history.

What port should the collector listen on?

2055 is the convention, but nothing enforces it. 9995 and 9996 are also common. Match the router config to whatever your collector expects.

Can I run NetFlow and a packet capture at the same time?

Yes. They answer different questions. NetFlow tells you which conversations exist and how big they are; a capture tells you what was inside them.

Does NetFlow see traffic the router drops with an ACL?

Input NetFlow is processed before the outbound ACL, so denied traffic can still appear in the cache. That is useful (you can see what is being blocked) and occasionally confusing (the flow shows bytes that never reached the destination).

Key Takeaways

  • Three objects: record (what), exporter (where), monitor (glue plus cache). Only the monitor is applied to an interface.
  • match = key field = flow identity. collect = non-key field = what you count. The record's "total field space" is the memory cost of your choices.
  • Apply the monitor input AND output, or you will only see half of every conversation.
  • NetFlow v9 sends a template packet and data packets separately. If the collector misses the template it decodes nothing, so keep template data timeout low.
  • Verify at three layers: show flow monitor cache (the router saw it), show flow exporter statistics (the router sent it), and the collector itself (it arrived and decoded).
  • On this IOL-XE image the cache timeouts cannot be tuned and sit at the 1800s/15s defaults. On production hardware, tune them.

Next: NetFlow v5 vs v9 vs FNF vs IPFIX, or the IP Services cluster guide.

Read next

Terminal card showing a repeating CDP native VLAN mismatch log naming Ethernet0/0 native VLAN 1 against SW2 Ethernet0/0 native VLAN 99
VLANs ·

Native VLAN Mismatch: Read the CDP Log, Fix the Trunk

%CDP-4-NATIVE_VLAN_MISMATCH hands you both interfaces and both native VLANs in one line. The real damage is underneath it: untagged frames get re-homed at the trunk boundary and two VLANs quietly become one. Captured live on IOS XE 17.18.2 in CML.