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

Flexible NetFlow tells you who is making the link busy, talking to what, on which port. Build the record, monitor, and exporter on Cisco IOS XE, export flows to a real Linux collector, and decode them so you see the data arrive.

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

Updated October 2026: I re-tested Flexible NetFlow on IOL 17.18.2 with an Alpine nfdump collector, including a wrong-source exporter fault (zero accepted flows), a corrected 12-flow transfer, and a 32-port scan as 64 one-packet flows. Platform corrections are in the new section before Key Takeaways.

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 IOL 17.18.2 (IOS XE) built in my home lab, exports the flows to an nfcapd collector on Alpine Linux, 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:        User defined
  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 is the field storage for your chosen match and collect set. It is not the per-flow memory cost and not the cache allocation: on this IOL build the same monitor allocated 4096 entries / 491,584 bytes (about 120 bytes per entry) beside that 54-byte field space. Add more key fields and the field space 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 my home lab the collector is an Alpine Linux host at 10.63.99.100 running nfcapd, reached from R1 Ethernet0/1 (10.63.99.1).

R1(config)#flow exporter PINGLABZ-COLLECTOR
R1(config-flow-exporter)# destination 10.63.99.100
R1(config-flow-exporter)# source Ethernet0/1
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/1 pins the source address of the export packets, which matters because collectors identify exporters by source IP. In my lab the collector was set to accept exports from 10.63.99.1 only. When I moved the source to Loopback63 (10.63.255.1), valid template and data packets still reached it, and it accepted 0 flows (see the re-test below). template data timeout 60 controls how often the v9 template is resent, which matters for a collector that might restart; I did not measure that timing in this lab. More on templates in the NetFlow versions comparison.

R1#show flow exporter PINGLABZ-COLLECTOR
Flow Exporter PINGLABZ-COLLECTOR:
  Description:              User defined
  Export protocol:          NetFlow Version 9
  Transport Configuration:
    Destination type:       IP
    Destination IP address: 10.63.99.100
    Source IP address:      10.63.99.1
    Source Interface:       Ethernet0/1
    Transport Protocol:     UDP
    Destination Port:       2055
    Source Port:            49868
    DSCP:                   0x0
    TTL:                    255
    Output Features:        Not Used
  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-flow-monitor)#exit
R1(config)#interface Ethernet0/2
R1(config-if)# ip flow monitor PINGLABZ-MON input
R1(config-if)# ip flow monitor PINGLABZ-MON output

In my lab, PINGLABZ-MON ran input and output on Ethernet0/2 and saw both directions of every conversation. NetFlow is directional: an input-only monitor on Ethernet0/2 saw just the generator-to-collector half of a ping. Adding a second input attachment on Ethernet0/1 caught the replies too, so two input points covered both directions as well. Pick one approach per observation point, and avoid counting the same packet twice.

R1#show flow monitor PINGLABZ-MON
Flow Monitor PINGLABZ-MON:
  Description:       User defined
  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 17.18.2 image used for this lab, cache timeout active, cache timeout inactive and cache entries were all rejected under the flow monitor with % Invalid input detected. The defaults (inactive 15 s, active 1,800 s, 4096 entries) are what you get. Many hardware and virtual-router platforms accept these commands, and lowering the active timeout is a common way to see long transfers while they are still running, but I did not test any other platform here. Treat timeout and entry tuning as platform-dependent.

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:                              12
  High Watermark:                               12
  Flows added:                                  36
  Flows aged:                                   24
    - Active timeout      (  1800 secs)          0
    - Inactive timeout    (    15 secs)         24
    - Event aged                                 0
    - Watermark aged                             0
    - Emergency aged                             0
IPV4 SRC ADDR    IPV4 DST ADDR    TRNS SRC PORT  TRNS DST PORT  IP TOS  IP PROT  intf input            intf output                     bytes long             pkts long  time abs first  time abs last
===============  ===============  =============  =============  ======  =======  ====================  ====================  ====================  ====================  ==============  =============
10.63.10.100     10.63.99.100                 0           2048  0x00          1  Et0/2                 Et0/1                                  252                     3    18:24:41.552   18:24:43.553
10.63.99.100     10.63.10.100                 0              0  0x00          1  Et0/1                 Et0/2                                  252                     3    18:24:41.553   18:24:43.553
10.63.10.100     10.63.99.100             48886           5201  0x00          6  Et0/2                 Et0/1                                 1411                    14    18:24:43.563   18:24:46.587
10.63.99.100     10.63.10.100              5201          48886  0x00          6  Et0/1                 Et0/2                                 1169                    13    18:24:43.564   18:24:46.587
10.63.10.100     10.63.99.100             48898           5201  0x00          6  Et0/2                 Et0/1                               415797                   342    18:24:43.568   18:24:46.587
10.63.99.100     10.63.10.100              5201          48898  0x00          6  Et0/1                 Et0/2                                12864                   230    18:24:43.568   18:24:46.586
10.63.10.100     10.63.99.100             48914           5201  0x00          6  Et0/2                 Et0/1                               410837                   338    18:24:43.573   18:24:46.587
10.63.99.100     10.63.10.100              5201          48914  0x00          6  Et0/1                 Et0/2                                10340                   188    18:24:43.574   18:24:46.586
10.63.10.100     10.63.99.100             48918           5201  0x00          6  Et0/2                 Et0/1                                 1222                    14    18:24:46.595   18:24:49.609
10.63.99.100     10.63.10.100              5201          48918  0x00          6  Et0/1                 Et0/2                                 1016                    13    18:24:46.596   18:24:49.609
10.63.10.100     10.63.99.100             58290           5201  0x00         17  Et0/2                 Et0/1                               384396                   314    18:24:46.599   18:24:49.597
10.63.99.100     10.63.10.100              5201          58290  0x00         17  Et0/1                 Et0/2                                   32                     1    18:24:46.600   18:24:46.600

Every row is a flow, and the key fields are doing exactly what they promised. Protocol 6 is TCP: the iperf3 control connections and two parallel data streams, with each direction a separate flow. Protocol 17 is UDP: the iperf3 UDP test at 384,396 bytes in 314 packets, plus one 32-byte packet back. Protocol 1 is ICMP, and note the port columns: ICMP has no ports, so the router puts the type and code there instead, which is why you see 2048 (type 8, echo request, shifted).

Look at the Flows aged counter: every aged flow left through the inactive timeout and none through the active timeout. Aged flows are what get exported, and by the end of this phase the exporter showed all 36 records added as sent.

And confirm the export actually happened:

R1#show flow exporter PINGLABZ-COLLECTOR statistics
Flow Exporter PINGLABZ-COLLECTOR:
  Packet send statistics (last cleared 00:05:17 ago):
    Successfully sent:         11                    (3618 bytes)
  Client send statistics:
    Client: Flow Monitor PINGLABZ-MON
      Records added:           36
        - sent:                36
      Bytes added:             1944
        - sent:                1944

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:

18:25:00.369477 IP (tos 0x0, ttl 255, id 61396, offset 0, flags [none], proto UDP (17), length 216)
    10.63.99.1.53681 > 10.63.99.100.2055: [udp sum ok] UDP, length 188
18:25:03.367815 IP (tos 0x0, ttl 255, id 61397, offset 0, flags [none], proto UDP (17), length 486)
    10.63.99.1.53681 > 10.63.99.100.2055: [udp sum ok] UDP, length 458
18:25:06.369045 IP (tos 0x0, ttl 255, id 61398, offset 0, flags [none], proto UDP (17), length 270)
    10.63.99.1.53681 > 10.63.99.100.2055: [udp sum ok] UDP, length 242

The sizes differ because each datagram carried a different number of flow records, not because one is a template packet and one a data packet. NetFlow v9 puts the template and the data in separate FlowSets, and those FlowSets can share one UDP datagram. In my capture, every export datagram carried a template FlowSet (flowset_id 0) and a data FlowSet together. So do not assume templates and data always travel in different UDP packets; they are separate FlowSets that may share one.

This is why template data timeout matters. If your collector restarts and misses the template, it cannot decode data until a template arrives again. I configured 60 seconds, but I did not measure template timing in this lab or test the 600-second default; in my captures the template FlowSet happened to ride in every export datagram.

Decoded on the Collector

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

Date first seen         Duration         Proto      Src IP Addr:Port          Dst IP Addr:Port     Flags  Tos  Packets    Bytes Flows
2026-10-21 18:24:41.552     00:00:02.001 ICMP      10.63.10.100:0     ->     10.63.99.100:8.0   ........    0        3      252     1
2026-10-21 18:24:41.553     00:00:02.000 ICMP      10.63.99.100:0     ->     10.63.10.100:0.0   ........    0        3      252     1
2026-10-21 18:24:43.563     00:00:03.024 TCP       10.63.10.100:48886 ->     10.63.99.100:5201  ........    0       14     1411     1
2026-10-21 18:24:43.564     00:00:03.023 TCP       10.63.99.100:5201  ->     10.63.10.100:48886 ........    0       13     1169     1
2026-10-21 18:24:43.568     00:00:03.019 TCP       10.63.10.100:48898 ->     10.63.99.100:5201  ........    0      342   415797     1
2026-10-21 18:24:43.568     00:00:03.018 TCP       10.63.99.100:5201  ->     10.63.10.100:48898 ........    0      230    12864     1
2026-10-21 18:24:43.573     00:00:03.014 TCP       10.63.10.100:48914 ->     10.63.99.100:5201  ........    0      338   410837     1
2026-10-21 18:24:43.574     00:00:03.012 TCP       10.63.99.100:5201  ->     10.63.10.100:48914 ........    0      188    10340     1
2026-10-21 18:24:46.600     00:00:00.000 UDP       10.63.99.100:5201  ->     10.63.10.100:58290 ........    0        1       32     1
2026-10-21 18:24:46.595     00:00:03.014 TCP       10.63.10.100:48918 ->     10.63.99.100:5201  ........    0       14     1222     1
2026-10-21 18:24:46.596     00:00:03.013 TCP       10.63.99.100:5201  ->     10.63.10.100:48918 ........    0       13     1016     1
2026-10-21 18:24:46.599     00:00:02.998 UDP       10.63.10.100:58290 ->     10.63.99.100:5201  ........    0      314   384396     1
Summary: total flows: 12, total bytes: 1.2 M, total packets: 1473, avg bps: 1.2 M, avg pps: 182, avg bpp: 841

Twelve flows, 1,239,588 bytes and 1,473 packets, and every one is a packet the router forwarded and reported. The two TCP flows of 415,797 and 410,837 bytes are the two parallel iperf3 streams. The 384,396-byte UDP flow is the UDP test. The asymmetry between each stream's two rows (415,797 bytes out, 12,864 back) is 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. On platforms that accept it, cache entries N raises the ceiling; on this IOL build it was rejected. 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 nothingCheck the export source first: in my lab a collector pinned to 10.63.99.1 accepted 0 flows when exports came from Loopback63. A template missed after a collector restart is another cause; lower template data timeout.
Only half the conversationMonitor applied input only on one interface. Add output, or an input monitor on the other interface; both covered both directions in my lab.

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?

It did in my lab. With an outbound ACL denying five UDP probes, the ingress monitor still recorded them as a flow (5 packets, 460 bytes) with output interface Null. That shows denied traffic can appear in the cache, but it does not prove the order the router processes NetFlow and the ACL in. It is useful (you can see what is being blocked) and occasionally confusing (the flow shows bytes that never reached the destination).

Re-tested in my home lab (October 2026)

Built in my home lab: Flexible NetFlow on Cisco IOL 17.18.2 exporting to nfcapd/nfdump 1.7.6 on Alpine Linux. Generator 10.63.10.100, R1 with the monitor on Ethernet0/2, collector 10.63.99.100 on UDP 2055. The collector only accepted exports from source 10.63.99.1, so a wrong exporter identity fails closed.

Lab diagram: Flexible NetFlow on an IOL 17.18.2 router built in my home lab. A generator at 10.63.10.100 sends traffic through R1, which runs the PINGLABZ-MON monitor on Ethernet0/2 in both directions and exports NetFlow v9 to an nfcapd collector at 10.63.99.100 on UDP 2055. The collector only accepts exports from 10.63.99.1. Exports sourced from Loopback63 (10.63.255.1) reached it but produced zero flows; sourcing them from Ethernet0/1 (10.63.99.1) gave 12 flows, 1,239,588 bytes and 1,473 packets. A 32-port scan burst showed up as 64 one-packet flows, and the record's 54-byte field space is shown next to the 4,096-entry, 491,584-byte cache allocation.

Break it: wrong exporter source

Pointing the exporter at Loopback63 (10.63.255.1) still put valid template/data UDP on the wire, but the source-pinned collector accepted nothing:

    Source IP address:      10.63.255.1
    Source Interface:       Loopback63
Summary: total flows: 0, total bytes: 0, total packets: 0, avg bps: 0, avg pps: 0, avg bpp: 0

Fix: restore Ethernet0/1 as source

Restoring source Ethernet0/1 yielded twelve decoded flows:

    Source IP address:      10.63.99.1
    Source Interface:       Ethernet0/1
Summary: total flows: 12, total bytes: 1239588, total packets: 1473, avg bps: 1230818, avg pps: 182, avg bpp: 841

Scan burst as many small flows

A 32-destination-port TCP scan (ports 20000–20031) produced 64 one-packet flows / 2,688 bytes. Treat this as evidence of many small port-specific flows — the record schema here does not carry TCP flags, so pair it with the generator transcript rather than claiming an IDS alert:

Not shown: 32 closed tcp ports (reset)
2026-10-21 18:26:52.276            0.766 any        10.63.10.100       32(50.0)       32(50.0)     1408(52.4)       41    14704    44
Summary: total flows: 64, total bytes: 2688, total packets: 64, avg bps: 28073, avg pps: 83, avg bpp: 42

IOL corrections worth keeping

Field space versus allocation:

  Total field space:  54 bytes
    Size:                 4096 entries / 491584 bytes

cache entries was rejected on this IOL build, and so were both cache timeout commands:

R1(config-flow-monitor)#cache entries 8192
% Invalid input detected at '^' marker.

Template and data FlowSets shared a datagram in the capture (one export packet from the first phase):

  flowset_id=0 kind=TEMPLATE length=56
  flowset_id=256 kind=DATA length=436

Outbound ACL-denied UDP probes still appeared in the ingress cache (five matches, five packets, 460 bytes, output Null). That matches the FAQ answer above, though it does not prove the internal processing order.

    10 deny udp host 10.63.10.100 host 10.63.99.100 eq 25252 (5 matches)
10.63.10.100     10.63.99.100             40000          25252  0x00         17  Et0/2                 Null                                   460                     5    18:26:07.320   18:26:07.321

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 field storage for those choices, not per-flow memory or the cache allocation (here: 54-byte fields beside 4096 entries / 491,584 bytes).
  • Cover both directions: in my lab, input plus output on one interface and input on two interfaces both saw both directions, while a single input-only monitor saw one. Avoid double-counting.
  • NetFlow v9 uses separate template and data FlowSets, and in my captures they shared one UDP datagram. If the collector misses the template it decodes nothing, so keep template data timeout low (I did not measure its timing here).
  • 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 17.18.2 image cache timeout and cache entries were rejected, leaving the 1800s/15s and 4096-entry defaults. Other platforms were not tested; tune them where your platform allows.

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

Read next