skip to content

In nftables, what is a named set and what is a verdict map, and why would you use them instead of a few hundred separate rules matching addresses or ports?

level: middleimportance: should knowfreq 50%

answer

  1. inline braces versus a declared object
  2. runtime membership without touching rules
  3. hash or tree instead of a linear walk
  4. flags: interval, timeout, dynamic
  5. key to verdict, applied by vmap

basics

~20 s

A named set is a typed, addressable collection declared in a table and referenced as @name; a verdict map pairs keys with verdicts and is used with vmap. Both replace long rule lists with one indexed lookup, and a named set can be updated at runtime without reloading the ruleset.

solid answer

~60 s

nftables has sets built into the language rather than bolted on. An **anonymous set** is written inline — `tcp dport { 22, 80, 443 } accept` — and belongs to that rule; changing it means rewriting the rule. A **named set** is declared in the table with a type (`ipv4_addr`, `inet_service`, `ether_addr`, or a concatenation like `ipv4_addr . inet_service`) and referenced as `@blocklist`, so you can add and remove elements at runtime with `nft add element` and `nft delete element` while the rule stays untouched. Flags extend it: `interval` for CIDR ranges, `timeout` for entries that expire themselves, `dynamic` for rules that populate the set as traffic arrives. A **map** goes further and stores key-to-value pairs; when the values are verdicts you use it with `vmap`, so one lookup dispatches directly to accept, drop or a jump target. The point is cost and manageability: a chain is walked rule by rule, while a set lookup is a single hashed (or tree) lookup that barely changes as the set grows.

code

bash · 14 lines
bash
nft add table inet filter
nft add chain inet filter input '{ type filter hook input priority filter; policy drop; }'

# named set with ranges and self-expiring entries
nft add set inet filter blocklist '{ type ipv4_addr; flags interval, timeout; }'
nft add element inet filter blocklist '{ 203.0.113.0/24, 198.51.100.7 timeout 1h }'
nft add rule inet filter input ip saddr @blocklist drop

# verdict map: one lookup dispatches per destination port
nft add map inet filter port_dispatch '{ type inet_service : verdict ; }'
nft add element inet filter port_dispatch '{ 80 : accept, 443 : accept, 25 : drop }'
nft add rule inet filter input tcp dport vmap @port_dispatch

nft list set inet filter blocklist

go deeper

for a junior

Know the inline brace form — tcp dport { 22, 80, 443 } — collapses several rules into one, and that nftables also has named sets you reference with an @ prefix.

for a middle

Declare a named set with a type and flags, add and remove elements at runtime, and explain a verdict map with vmap. Be clear on why a lookup beats walking a long chain of rules.

for a senior

Reach for timeout and dynamic sets to solve real problems — self-expiring bans, in-kernel rate limiting — and know the operational catch that a ruleset reload recreates sets empty.

for a principal

Decide where firewall state lives: sets updated by an automated feed versus a ruleset regenerated from configuration, who is allowed to write elements, and how set size limits keep an attacker from turning your blocklist into a memory problem.

## Rules are walked; sets are looked up A chain is an ordered list, and matching it is a linear walk: rule one, rule two, and so on until something produces a verdict. Fifty rules is nothing. Fifty thousand rules matching fifty thousand blocked addresses is a per-packet cost that scales with the size of your blocklist. A set is a data structure. nftables picks the backing implementation from the type and flags — a hash for plain values, a red-black tree for interval sets — so the lookup cost is roughly flat or logarithmic in the number of elements. One rule referencing a set with 100,000 addresses does one lookup. ## Anonymous sets The inline form is the one everyone writes first: ```bash nft add rule inet filter input tcp dport { 22, 80, 443 } accept ``` That set has no name and no independent existence — it is owned by the rule. It is perfect for a short, static list, and it already saves you three rules. What it cannot do is change: to add port 8443 you delete and re-add the rule. ## Named sets A named set is a first-class object in the table: ```bash nft add set inet filter blocklist '{ type ipv4_addr; flags interval, timeout; }' nft add element inet filter blocklist '{ 203.0.113.0/24, 198.51.100.7 timeout 1h }' nft add rule inet filter input ip saddr @blocklist drop ``` The rule is written once and never touched again. Membership changes are `nft add element` / `nft delete element`, which take effect immediately and do not disturb anything else in the ruleset — the crucial operational property, because reloading a whole ruleset to ban one address is both heavy-handed and risky. Useful declarations: - **`type`** — `ipv4_addr`, `ipv6_addr`, `inet_service` (ports), `ether_addr`, `inet_proto`, `mark`, and concatenations joined with `.`. - **`flags interval`** — the set may hold ranges and prefixes rather than only single values. - **`flags timeout`** plus a `timeout` on the set or per element — entries expire on their own, which is how you build a temporary ban list with no cleanup job. - **`flags dynamic`** — rules may add elements themselves, e.g. `add @recent { ip saddr }`, giving you rate-limit and knock-style behaviour entirely in the kernel. - **`flags constant`** with `elements = { ... }` — a fixed set the kernel can optimise harder. - **`size`** — a ceiling on element count, worth setting on any dynamic set so a flood cannot grow it without bound. This is the built-in replacement for the separate `ipset` tool the older stack needed. ## Maps and verdict maps A map stores keys and values rather than bare membership: ```bash nft add map inet filter port_dispatch '{ type inet_service : verdict ; }' nft add element inet filter port_dispatch '{ 22 : jump ssh_checks, 80 : accept, 25 : drop }' nft add rule inet filter input tcp dport vmap @port_dispatch ``` `vmap` performs the lookup and applies the resulting verdict directly. One rule replaces a dispatch ladder, and adding a service is an element insert rather than a rule edit at the right position. Maps can also produce non-verdict values — for example mapping an interface to a mark, or a destination to a NAT target — with the `map` keyword. ## Concatenations Types can be joined, which is what makes sets expressive enough to replace real rule lists: ```bash nft add set inet filter allowed '{ type ipv4_addr . inet_service ; }' nft add element inet filter allowed '{ 10.0.0.5 . 5432, 10.0.0.6 . 6379 }' nft add rule inet filter input ip saddr . tcp dport @allowed accept ``` One lookup answers "is this exact source allowed to reach this exact port", a question that would otherwise be one rule per pair. ## Inspecting and maintaining them `nft list set inet filter blocklist` prints the current contents; `nft list ruleset` includes set declarations, and with `-a` you get handles. Remember that a full ruleset reload recreates sets — dynamically accumulated elements are lost unless the file declares them or something repopulates them, which is a genuine consideration for a long-lived ban list. ## When not to reach for one A set is the wrong tool when each entry needs its own distinct treatment — different logging, different rate limits, different jump targets — unless a verdict map expresses that cleanly. And a three-element static list is perfectly served by the anonymous form; declaring a named set for it adds an object to manage for no benefit.

  • How would you build a ban list that forgets entries by itself, with no cron job cleaning up?
    Declare the set with `flags timeout` and give each element a timeout — `nft add element inet filter blocklist { 198.51.100.7 timeout 1h }` — or set a default timeout on the set. The kernel expires elements on its own. Add `flags dynamic` and a `size` limit if rules themselves populate the set, so a flood cannot grow it without bound.
  • What happens to the contents of a dynamically populated set when you reload the ruleset from a file?
    They are gone. A reload that flushes and recreates the table recreates the set empty, keeping only elements the file declares. For a ban list that matters: either accept the reset, dump the set with `nft list set` and re-add the elements after loading, or scope the reload so the table holding the set is not replaced.
  • When is a long list of individual rules still the better choice over a set?
    When the entries need different treatment — separate counters, distinct log prefixes, different rate limits — since a set answers only membership. A verdict map covers the case where the difference is just which verdict or jump target applies. And for three static values the inline anonymous set is clearer than a declared object nobody else references.

Matching a long chain of rules is reading a guest list from the top every time someone arrives; a named set is the same list sorted and indexed, so the doorman checks one place regardless of how many names are on it.

saying these in an interview costs you the question

  • Thinks nftables still needs the separate ipset tool
  • Says an anonymous set can be updated at runtime
  • Believes set lookup cost grows linearly like rules
  • Confuses a map with a set of key-only elements
  • Forgets a ruleset reload empties dynamically filled sets

context