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.
match / collectThe 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:
matchdefines 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.collectdefines 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 outputVerify 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 lastTotal 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 60Two 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: 60The 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 outputIn 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 secsThose two timeouts govern when a flow leaves the cache and gets exported:
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.600Every 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: 1944If 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 242The 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: 841Twelve 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 samplertells 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 Nraises the ceiling; on this IOL build it was rejected. Watch theHigh Watermarkin 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
show flow interface.Successfully sent: 0. Check routing to the collector.template data timeout.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.

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: Loopback63Summary: total flows: 0, total bytes: 0, total packets: 0, avg bps: 0, avg pps: 0, avg bpp: 0Fix: restore Ethernet0/1 as source
Restoring source Ethernet0/1 yielded twelve decoded flows:
Source IP address: 10.63.99.1
Source Interface: Ethernet0/1Summary: total flows: 12, total bytes: 1239588, total packets: 1473, avg bps: 1230818, avg pps: 182, avg bpp: 841Scan 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: 42IOL corrections worth keeping
Field space versus allocation:
Total field space: 54 bytes
Size: 4096 entries / 491584 bytescache 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=436Outbound 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.321Key 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 timeoutlow (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 timeoutandcache entrieswere 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.