nftables sets are the direct successor to ipset for managing collections of IP addresses, networks, and other data for fast lookups in firewall rules.

# nft list ruleset
table ip filter {
    set ip_blacklist {
        type ipv4_addr
        flags interval
        elements = { 192.168.1.1, 10.0.0.0/8, 1.2.3.4 }
    }

    chain input {
        type filter hook input priority 0; policy accept;

        ip saddr @ip_blacklist drop
    }
}

This nftables ruleset demonstrates a set named ip_blacklist within the ip filter table. The type ipv4_addr specifies that elements are IPv4 addresses. The flags interval allows for ranges and CIDR blocks to be stored, not just individual IPs. The elements are the actual IP addresses and networks being tracked. The chain input then uses this set: ip saddr @ip_blacklist drop means "if the source IP address of an incoming packet is found within the ip_blacklist set, drop the packet."

The primary problem nftables sets solve is efficient IP address management for firewall rules. Before nftables and ipset, you’d often have to write individual rules for every single IP or network you wanted to block or allow. This quickly became unmanageable for large lists. ipset introduced the concept of a "set" of IPs that the kernel could query very quickly. nftables generalizes this concept to be a first-class citizen of the firewall language itself, integrating seamlessly with its other features like connection tracking and stateful inspection.

Internally, nftables sets are implemented using highly optimized data structures, often hash tables or radix trees, depending on the set type and flags. This allows for average O(1) or O(log N) lookups, meaning the time it takes to check if an IP is in the set doesn’t grow significantly even as the set gets very large. This is crucial for high-performance packet filtering where every microsecond counts.

The type parameter is fundamental. It dictates what kind of data the set can hold:

  • type ipv4_addr: For IPv4 addresses.
  • type ipv6_addr: For IPv6 addresses.
  • type ether_addr: For MAC addresses.
  • type inet_addr: For both IPv4 and IPv6 addresses, a convenient shortcut.
  • type int: For integers.
  • type string: For strings.
  • type flow: For nftables flow identifiers.

You can also combine types with bitmasks, e.g., type ipv4_addr . inet_service to store IP address and port pairs.

The flags parameter controls how elements are stored and matched:

  • flags interval: Allows storage of IP ranges and CIDR blocks, not just individual IPs. This is crucial for ipset compatibility and general usefulness.
  • flags timeout=SECONDS: Makes the set elements expire after a specified duration, useful for temporary blocks.
  • flags constant: Indicates the set will not be modified after creation, allowing for further kernel optimizations.
  • flags nomatch: Inverts the set’s meaning; elements in the set are not matched.

You manage sets using nft commands:

Creating a set:

nft add set ip filter ip_allowlist { type ipv4_addr\; }

This creates an empty set named ip_allowlist of type ipv4_addr in the ip filter table.

Adding elements:

nft add element ip filter ip_allowlist { 192.168.1.100, 10.1.0.0/16 }

This adds a single IP and a subnet to the ip_allowlist.

Deleting elements:

nft delete element ip filter ip_allowlist { 192.168.1.100 }

Listing set elements:

nft list set ip filter ip_allowlist

Deleting an entire set:

nft delete set ip filter ip_allowlist

When using flags interval, nftables treats elements like 10.0.0.0/8 as a range that covers all IPs from 10.0.0.0 to 10.255.255.255. When an incoming packet’s source IP is checked against this set, the kernel performs a highly efficient lookup to see if the packet’s IP falls within any of the stored ranges or matches any of the individual IPs. This is substantially faster than iterating through hundreds or thousands of individual ip saddr X.X.X.X accept rules.

The type inet_addr is particularly useful because it can hold both IPv4 and IPv6 addresses in the same set. When you use inet_addr and add an IPv4 address, it’s stored as such. If you add an IPv6 address, it’s stored as an IPv6 address. nftables intelligently handles the distinction during lookups.

nftables sets can also contain composite types, for example, type ipv4_addr . tcp dport. This allows you to create sets of IP address and destination port pairs. A rule might then look like tcp dport @syn_flood_ports drop, where @syn_flood_ports contains entries like 192.168.1.50 . 80 and 10.0.0.10 . 443.

The flags interval is not just for CIDR blocks; it also enables range matching for individual IPs. For example, if you have a set with flags interval and you add 192.168.1.100, it’s interpreted as the single IP. However, if you add 192.168.1.100-192.168.1.110, it creates a contiguous range for matching. This is a subtle but powerful extension of ipset’s capabilities, allowing for more flexible definition of IP groups.

A common pitfall is forgetting to specify flags interval when you intend to use CIDR notation. If you create a set with type ipv4_addr but without flags interval and try to add 10.0.0.0/8, nftables will reject it or store it as a literal string that won’t match anything. You must include flags interval for nftables to interpret and match CIDR blocks or IP ranges correctly.

The next concept you’ll likely encounter is nftables maps, which are similar to sets but allow you to associate a value with each key (IP, port, etc.), enabling more complex conditional logic based on the lookup result.

Want structured learning?

Take the full Nftables course →