Managing a small Linux firewall is relatively easy. A few IP addresses, several ports, and a handful of accept or drop rules may be enough.
The problem begins when the network grows.
Imagine a firewall that must manage 50 trusted administrators, 200 blocked IP addresses, multiple server networks, several VLANs, dozens of TCP and UDP services, different NAT destinations, and separate security policies for LAN, DMZ, VPN, and management networks.
Writing one nftables rule for every address or service quickly produces a ruleset that is difficult to read, difficult to maintain, and easy to misconfigure.
This is exactly the problem that nftables sets and maps are designed to solve.
Instead of repeating almost identical rules, nftables allows administrators to group values into sets and associate keys with values or actions through maps. The official Netfilter project specifically identifies maps and concatenations as mechanisms that can reduce linear ruleset inspection and improve ruleset scalability.
For example, instead of writing:
ip saddr 192.168.30.10 tcp dport 22 accept
ip saddr 192.168.30.11 tcp dport 22 accept
ip saddr 192.168.30.12 tcp dport 22 accept
ip saddr 192.168.30.13 tcp dport 22 accept you can create one set:
set trusted_admins {
type ipv4_addr
elements = {
192.168.30.10,
192.168.30.11,
192.168.30.12,
192.168.30.13
}
} and then use a single firewall rule:
ip saddr @trusted_admins tcp dport 22 accept That simple change represents one of the most important transitions from a basic nftables firewall to a scalable production ruleset.
This guide explains nftables sets, maps, interval sets, concatenations, verdict maps, dynamic sets, timeouts, IPv4 and IPv6 sets, port sets, and troubleshooting techniques, with practical examples that can be adapted to real Linux firewall environments.
If you are building a stateful firewall, this article should be used alongside our nftables Conntrack States and Troubleshooting guide because sets and maps are commonly combined with ct state new, established, related, and invalid policies.
An nftables set is a collection of values that can be referenced by firewall rules.
Instead of checking one value at a time with multiple rules, a firewall can test whether a packet attribute belongs to a set.
A set might contain:
IP addresses
IPv6 addresses
TCP ports
UDP ports
protocol values
network prefixes
interface-related values
or combinations of supported data types The Netfilter project’s nftables material describes a generic set infrastructure that supports sets built from supported nftables data types.
Conceptually:
Packet
↓
Source IP
↓
Is source inside @trusted_admins?
├── YES → continue with trusted policy
└── NO → continue with normal policy This allows a large number of values to be represented by a relatively small number of firewall rules.
Consider a network with ten administrator workstations.
Without a set, you could write:
ip saddr 192.168.30.10 tcp dport 22 accept
ip saddr 192.168.30.11 tcp dport 22 accept
ip saddr 192.168.30.12 tcp dport 22 accept
ip saddr 192.168.30.13 tcp dport 22 accept
ip saddr 192.168.30.14 tcp dport 22 accept and continue adding rules.
The problem is not merely the number of lines.
Every duplicated rule becomes another configuration object that must be:
reviewed
updated
audited
troubleshot
documented With a set, the firewall policy becomes:
Trusted administrators
↓
SSH
↓
Server rather than a long list of nearly identical rules.
This is easier for humans to understand and easier to maintain.
nftables supports both anonymous and named set usage.
An anonymous set is defined directly inside a rule.
For example:
tcp dport { 22, 80, 443 } accept This means:
TCP/22
TCP/80
TCP/443
↓
ACCEPT instead of:
tcp dport 22 accept
tcp dport 80 accept
tcp dport 443 accept Official Netfilter nftables examples demonstrate this form using braces directly inside a rule.
Anonymous sets are excellent when:
the list is small
the values rarely change
the set is used only once For example:
tcp dport { 22, 80, 443 } accept is simple and readable.
When the same values are used repeatedly or need to be managed independently, a named set is usually better.
Example:
table inet firewall {
set trusted_admins {
type ipv4_addr
elements = {
192.168.30.10,
192.168.30.11,
192.168.30.12
}
}
chain input {
type filter hook input priority filter;
policy drop;
ip saddr @trusted_admins tcp dport 22 accept
}
} The @ indicates that the rule references a named set:
@trusted_admins The policy now reads almost like documentation:
IF source belongs to trusted_admins
AND destination port is SSH
THEN accept That is considerably easier to understand than dozens of repeated IP-specific rules.
You can create a set using the nft command.
For example:
sudo nft add set inet firewall trusted_admins '{ type ipv4_addr; }' Then add elements:
sudo nft add element inet firewall trusted_admins \
'{ 192.168.30.10, 192.168.30.11 }' Then reference the set:
sudo nft add rule inet firewall input \
ip saddr @trusted_admins tcp dport 22 accept The exact syntax you use in production may differ depending on whether you manage nftables interactively or through a persistent configuration file.
For production servers, maintaining a reviewed persistent ruleset is generally easier to audit than building the entire firewall manually after every reboot.
To inspect the complete ruleset:
sudo nft list ruleset To inspect a specific table:
sudo nft list table inet firewall You can also inspect the relevant set while troubleshooting.
This is important because one of the first questions when a set-based rule fails should be:
Does the set actually contain the value I expect?
Do not assume it does.
Verify it.
Suppose a new administrator workstation receives:
192.168.30.15 Instead of creating another firewall rule, add the address to the set:
sudo nft add element inet firewall trusted_admins \
'{ 192.168.30.15 }' The existing firewall rule:
ip saddr @trusted_admins tcp dport 22 accept does not need to change.
This separation is extremely valuable.
You have separated:
POLICY
trusted administrators may use SSH from:
DATA
these addresses are trusted administrators That is a much more scalable firewall design.
If an administrator no longer needs access:
sudo nft delete element inet firewall trusted_admins \
'{ 192.168.30.15 }' Again, the firewall policy itself remains unchanged.
You modify membership rather than rewriting the firewall architecture.
Sets are not limited to IP addresses.
Suppose a server should expose:
SSH
HTTP
HTTPS Instead of:
tcp dport 22 accept
tcp dport 80 accept
tcp dport 443 accept use:
tcp dport { 22, 80, 443 } accept For a reusable named set:
set allowed_tcp_ports {
type inet_service
elements = { 22, 80, 443 }
} Then:
tcp dport @allowed_tcp_ports accept This becomes particularly useful when a server or security zone has a defined service profile.
Consider a DMZ web server.
Its public service profile might contain:
HTTP → TCP/80
HTTPS → TCP/443 You could define:
set web_ports {
type inet_service
elements = { 80, 443 }
} and use:
tcp dport @web_ports accept This makes the ruleset communicate intent:
Allow web services rather than:
Allow 80
Allow 443 For a complete segmented firewall architecture, combine this technique with our Linux DMZ with nftables guide.
For IPv4 addresses, a typical set uses:
type ipv4_addr Example:
set management_hosts {
type ipv4_addr
elements = {
192.168.30.10,
192.168.30.20,
192.168.30.30
}
} Then:
ip saddr @management_hosts tcp dport 22 accept You can use this pattern for:
trusted administrators
monitoring servers
DNS servers
application servers
VPN clients
blocked addresses depending on your firewall architecture.
A dual-stack firewall should not treat IPv6 as an afterthought.
For IPv6 values, use an appropriate IPv6 address type, for example:
set trusted_ipv6_admins {
type ipv6_addr
elements = {
2001:db8:30::10,
2001:db8:30::20
}
} Then:
ip6 saddr @trusted_ipv6_admins tcp dport 22 accept One advantage of the inet family is that IPv4 and IPv6 filtering can be organized within the same table while using protocol-specific expressions such as:
ip and:
ip6 where appropriate.
Suppose you want to match entire networks rather than individual addresses.
For example:
192.168.10.0/24
192.168.20.0/24
192.168.30.0/24 This is where interval-capable sets become useful.
Conceptually:
set internal_networks {
type ipv4_addr
flags interval
elements = {
192.168.10.0/24,
192.168.20.0/24,
192.168.30.0/24
}
} You can then write:
ip saddr @internal_networks accept when that matches your intended policy.
The important benefit is that the firewall can represent network ranges as structured data instead of duplicating rules for each subnet.
A set named:
trusted_networks may look harmless.
But if it contains:
192.168.0.0/16 you have potentially trusted tens of thousands of addresses.
The fact that sets make rules concise does not make the underlying policy safe.
Always ask:
What exactly belongs to this set?
Why is it trusted?
Which services does membership unlock?
Can the set become broader than intended? Readability should strengthen security review, not replace it.
Sets become particularly valuable in segmented networks.
Imagine:
VLAN 10 → Users
192.168.10.0/24
VLAN 20 → Servers
192.168.20.0/24
VLAN 30 → Management
192.168.30.0/24
VLAN 40 → IoT
192.168.40.0/24 You might define:
set user_networks {
type ipv4_addr
flags interval
elements = { 192.168.10.0/24 }
}
set server_networks {
type ipv4_addr
flags interval
elements = { 192.168.20.0/24 }
}
set management_networks {
type ipv4_addr
flags interval
elements = { 192.168.30.0/24 }
} Then firewall rules can express relationships between zones.
For example:
Management
↓
SSH
↓
Servers instead of hardcoding addresses throughout the ruleset.
For the underlying routing and security model, see Linux Inter-VLAN Routing with Network Namespaces and Linux nftables Inter-VLAN Firewall.
A set answers whether a key belongs to a collection.
A map associates a key with another value.
Conceptually:
SET
Key
↓
Present?
↓
YES / NO while a map behaves more like:
MAP
Key
↓
Lookup
↓
Value For example:
source network
↓
lookup
↓
specific translated address The official Netfilter nftables material demonstrates maps by associating source networks with corresponding SNAT addresses.
Maps therefore extend the basic set concept into key/value relationships.
The difference is easier to understand with a simple example.
A set might represent:
22
80
443 and answer:
Is destination port inside this collection? A map could represent:
22 → SSH policy
80 → Web policy
443 → Secure web policy and answer:
What value or action corresponds to this key? In short:
SET
key → membership
MAP
key → value This distinction becomes very powerful in larger rulesets.
Consider a firewall with many networks.
Without maps, you may create a sequence of rules:
If source = Network A → action A
If source = Network B → action B
If source = Network C → action C
If source = Network D → action D This is linear rule logic.
With an appropriate map:
Source
↓
Map lookup
↓
Associated result The ruleset can become both smaller and easier to understand.
The official nftables project specifically lists maps and concatenations among its high-performance features and notes that linear ruleset inspection does not scale well.
One of the clearest map examples involves NAT.
Imagine:
192.168.10.0/24
↓
Public Address A
192.168.20.0/24
↓
Public Address B Instead of creating separate SNAT rules for every source network, a map can associate:
source network → translated address Conceptually:
snat ip saddr map {
192.168.10.0/24 : 203.0.113.10,
192.168.20.0/24 : 203.0.113.20
} This kind of design becomes increasingly useful as the number of mappings grows.
The exact NAT design should still be tested carefully because routing, conntrack, and NAT hooks all affect the final packet path.
If NAT itself is the problem, use our nftables DNAT Port Forwarding Not Working troubleshooting guide before assuming the map is responsible.
One of the most powerful nftables concepts is the verdict map, often called a vmap.
A normal map can associate:
key → value A verdict map can associate a key with firewall control flow or a verdict.
Conceptually:
Key
↓
Lookup
↓
ACCEPT / DROP / JUMP This allows multiple policy decisions to be represented compactly.
Imagine:
TCP/22 → SSH chain
TCP/80 → Web chain
TCP/443 → Web chain Instead of writing several independent rules, a verdict map can direct matching traffic according to the destination port.
Conceptually:
tcp dport vmap {
22 : jump ssh_policy,
80 : jump web_policy,
443 : jump web_policy
} Now the firewall becomes:
Packet
↓
Destination Port
↓
Verdict Map
├── 22 → SSH policy
├── 80 → Web policy
└── 443 → Web policy This is particularly valuable for building modular firewall architectures.
Consider:
Rule 1 → TCP/22
Rule 2 → TCP/80
Rule 3 → TCP/443
Rule 4 → TCP/8080
Rule 5 → TCP/8443
... As the ruleset grows, traffic may need to be tested against many rules.
A verdict-map architecture can instead perform a lookup and direct traffic to the relevant policy.
This is one of the reasons sets, maps, and concatenations are central to scalable nftables design.
However, fewer lines do not automatically mean a better firewall.
The resulting policy still needs to remain understandable.
Sometimes one value is not enough to identify a policy.
Suppose:
192.168.10.50 → TCP/443 should be allowed, while:
192.168.10.50 → TCP/22 should not.
Matching only the source IP is insufficient.
Matching only the port is also insufficient.
You need the combination:
Source IP + Destination Port nftables can work with concatenated keys.
Conceptually:
192.168.10.50 . 443
192.168.30.10 . 22
192.168.40.20 . 53 Now the firewall can make decisions based on combinations rather than single fields.
Traditional rule design often looks like:
source?
↓
destination?
↓
protocol?
↓
port?
↓
action A concatenated key can represent several of those dimensions together:
source . destination . port
↓
lookup This can significantly simplify large policy matrices.
The nftables project explicitly highlights concatenations together with maps as a way to improve scalability compared with long linear rule inspection.
Suppose the policy is:
192.168.30.10 → TCP/22
192.168.10.50 → TCP/443 You can conceptually represent permitted combinations as:
192.168.30.10 . 22
192.168.10.50 . 443 The advantage is important.
You are not saying:
192.168.30.10 can access everything and you are not saying:
everyone can access TCP/22 You are defining an exact combination.
This is extremely useful in zero-trust-like segmentation and tightly controlled inter-VLAN policies.
Sets become even more powerful when combined with connection tracking.
For example:
ct state established,related accept can handle existing flows.
Then sets can define which systems are allowed to create new flows:
ct state new \
ip saddr @management_hosts \
tcp dport 22 \
accept The architecture becomes:
Existing connection?
↓
ESTABLISHED / RELATED
↓
ACCEPT
New connection?
↓
Source belongs to @management_hosts?
↓
TCP/22?
↓
ACCEPT This is much more precise than simply allowing all packets from a trusted network.
For a deeper explanation of why state matters, see nftables Conntrack States and Troubleshooting.
Sets can also make logging more meaningful.
Suppose you maintain:
@management_networks and want to log unexpected SSH attempts from outside those networks.
Instead of creating multiple IP-specific logging rules, you can build the logging policy around set membership.
Then combine it with the rate-limited logging techniques explained in our nftables logging guide.
A strong production architecture is:
Sets
↓
Policy classification
↓
Conntrack
↓
Counters
↓
Rate-limited logging
↓
Verdict This separates data, state, observability, and security decisions.
So far, the examples have used relatively static sets.
But nftables can also work with dynamic set behavior.
This enables a very different architecture:
Packet arrives
↓
Extract key
↓
Add/update set state
↓
Temporary membership
↓
Policy changes based on that state This is useful for advanced controls such as temporary tracking of sources.
A static set might contain:
trusted administrators
known DNS servers
server networks
approved service ports These values are usually configured administratively.
A dynamic set may represent:
recent connection sources
temporary blocked addresses
sources observed during a time window The distinction is important.
Static sets describe configuration.
Dynamic sets can describe changing runtime state.
Timeouts make temporary membership possible.
Conceptually:
IP address added
↓
Set membership
↓
Timeout expires
↓
Address removed automatically This becomes useful when the firewall needs temporary state rather than permanent configuration.
For example:
Source detected
↓
temporary set
↓
30-second membership
↓
automatic expiration The exact timeout should depend on the purpose of the policy.
Do not choose arbitrary values simply because an example uses them.
Without expiration, a dynamically populated set can continue growing or retain entries longer than intended.
Timeouts provide a lifecycle:
CREATE
↓
ACTIVE
↓
EXPIRE
↓
REMOVE This makes dynamic firewall state much easier to manage.
This is where nftables becomes especially interesting.
Suppose you want to track connection attempts by source.
A simple global limiter answers:
How much matching traffic is allowed overall? But sometimes you need:
How much traffic is coming from each source? Dynamic sets, meters, or related keyed-state mechanisms can support policies where each source is evaluated independently.
This is particularly relevant to:
SSH connection attempts
API exposure
service abuse
scan-like behavior and will be the subject of the next article in this CorgiNetwork series.
Consider:
Client A
Client B
Client C A global limiter can behave conceptually like:
A + B + C
↓
shared threshold But a per-source policy should behave like:
A → own state
B → own state
C → own state Those are fundamentally different designs.
This distinction becomes important when combining sets with the rate-limiting techniques introduced in the previous article.
A common use case is a blocklist:
set blocked_hosts {
type ipv4_addr
elements = {
198.51.100.10,
198.51.100.20
}
} Then:
ip saddr @blocked_hosts drop This is much cleaner than:
ip saddr 198.51.100.10 drop
ip saddr 198.51.100.20 drop especially when the list becomes large.
An IP set can be useful, but it is not a replacement for:
secure authentication
software patching
service hardening
application security
network segmentation
monitoring Attack sources can change addresses.
Legitimate systems can also use shared or changing addresses.
Use address-based controls as one part of a larger security model.
A stronger use case is defining administrative boundaries.
For example:
set management_networks {
type ipv4_addr
flags interval
elements = {
192.168.30.0/24,
10.50.0.0/24
}
} Then:
ip saddr @management_networks tcp dport 22 accept Now the firewall explicitly communicates:
SSH administration
↓
only from
↓
management networks This is much easier to audit.
Suppose clients should only use approved DNS resolvers:
192.168.20.53
192.168.20.54 Define:
set dns_servers {
type ipv4_addr
elements = {
192.168.20.53,
192.168.20.54
}
} Now DNS policy can reference:
@dns_servers instead of duplicating the resolver addresses throughout the ruleset.
Likewise:
set web_servers {
type ipv4_addr
elements = {
192.168.20.10,
192.168.20.11,
192.168.20.12
}
} Then a management rule could conceptually allow:
Management
↓
@web_servers
↓
SSH while user traffic could allow:
Users
↓
@web_servers
↓
HTTP / HTTPS One server set can therefore participate in several clearly separated policies.
Consider:
Internet
↓
Firewall
↓
DMZ
↓
Internal Network You might maintain:
@dmz_web_servers
@dmz_dns_servers
@management_hosts
@public_web_ports The firewall policy becomes much more descriptive.
Instead of:
192.168.20.10
192.168.20.11
80
443
192.168.30.10 you read:
management_hosts
dmz_web_servers
public_web_ports This reduces the cognitive load required to review the firewall.
For a full implementation model, see Linux DMZ with nftables.
Sets and maps can also improve NAT architectures.
Suppose multiple internal web servers are published through different rules.
Rather than scattering server addresses throughout the ruleset, structured objects can help centralize important address data.
But remember that NAT changes packet attributes.
A packet might enter as:
203.0.113.10:443 and after DNAT become:
192.168.20.10:443 Therefore, a set lookup must be written with awareness of where in the packet path it occurs.
If a set-based NAT rule appears correct but does not match, inspect the actual packet at that hook.
For this, see nftables DNAT Port Forwarding Not Working.
Hairpin NAT adds another layer of complexity.
The path may look like:
LAN Client
↓
Public IP
↓
DNAT
↓
Internal Server with SNAT or masquerading required depending on the topology.
A set can simplify groups of:
internal clients
published servers
public service ports but it cannot fix an incorrect NAT path.
If the service works from the Internet but fails when an internal client uses the public IP, use our nftables Hairpin NAT and NAT Loopback guide.
Do not confuse firewall sets with routing policy.
nftables may classify traffic, but Linux routing still determines where packets go.
Suppose a packet is accepted because:
source ∈ @trusted_networks but Linux selects the wrong gateway.
The firewall rule can be perfectly correct while the connection still fails.
If multiple routing tables or gateways are involved, continue with Linux Policy Routing with ip rule and Multiple Routing Tables.
Consider:
Request
Client
↓
eth0
↓
Server
Reply
Server
↓
eth1
↓
Client A firewall set does not solve this routing asymmetry.
The result can interact with:
conntrack
rp_filter
stateful firewall rules
NAT If you see unexpected INVALID traffic or missing replies, read Linux rp_filter and Asymmetric Routing.
If Linux is selecting an unexpected NIC, use Linux Traffic Uses the Wrong Interface.
Similarly, a perfect nftables set cannot fix Layer 2 behavior.
On multi-homed Linux hosts, ARP behavior may cause traffic to reach an unexpected interface or produce confusing address resolution.
If several NICs share the same subnet and firewall behavior appears inconsistent, see Linux ARP Flux with Multiple NICs on the Same Subnet.
Always troubleshoot at the correct layer.
Sets are especially useful in lab and container-like networking environments involving:
Linux bridges
veth pairs
network namespaces
VLAN interfaces For example, a firewall might define sets for:
namespace subnets
container addresses
bridge networks
service ports and then enforce policies between them.
For the underlying topology, see Linux Bridge Networking with Network Namespaces and veth.
Suppose:
ip saddr @trusted_admins tcp dport 22 accept does not appear to work.
Do not immediately rewrite the firewall.
Use a structured workflow.
First verify the set:
sudo nft list ruleset Confirm that the expected source address is actually present.
Then verify the packet:
sudo tcpdump -ni any tcp port 22 Ask:
What is the real source IP?
What interface receives the packet?
Is NAT changing the address?
Is the traffic IPv4 or IPv6?
Does an earlier rule match first?
What conntrack state does the packet have? This is far more reliable than guessing.
Counters are one of the easiest ways to determine whether a set rule matches.
For example:
ip saddr @trusted_admins \
tcp dport 22 \
counter \
accept Then:
sudo nft list chain inet firewall input If the counter increases:
packet reached the rule
+
set membership matched
+
other expressions matched If it remains zero, investigate the packet path and matching conditions.
For temporary troubleshooting:
ip saddr @trusted_admins \
tcp dport 22 \
limit rate 5/second \
log prefix "NFT_ADMIN_SSH: " can help identify matching traffic.
But do not turn a high-volume set rule into unlimited packet logging.
For production observability, combine:
sets
+
counters
+
rate-limited logging
+
conntrack rather than logging everything.
nft monitor trace for Difficult CasesWhen a large ruleset contains:
sets
maps
verdict maps
jump chains
NAT
conntrack
VLANs it may be difficult to understand the exact packet path from the configuration alone.
Targeted nftables tracing can help.
A common diagnostic tool is:
sudo nft monitor trace after selectively enabling tracing for the flow under investigation.
Do not enable broad tracing unnecessarily on a busy production firewall.
Narrow the test using:
source
destination
protocol
port and trace only what you need.
Bad design:
ip saddr 192.168.30.10 tcp dport 22 accept
ip saddr 192.168.30.11 tcp dport 22 accept
ip saddr 192.168.30.12 tcp dport 22 accept
ip saddr 192.168.30.13 tcp dport 22 accept Better:
ip saddr @trusted_admins tcp dport 22 accept The set separates membership from policy.
Sets improve organization, but excessive abstraction can make a small firewall harder to understand.
You probably do not need:
@server1
@server2
@port22
@port443
@single_admin when each object contains only one permanent value and is used once.
Use sets when they improve:
readability
reuse
scalability
management not simply because nftables supports them.
Avoid:
@set1
@group2
@ips
@allowed Prefer:
@management_hosts
@web_servers
@dns_servers
@blocked_sources
@internal_networks
@public_web_ports A good ruleset should explain itself.
Consider:
@allow What does it contain?
Addresses?
Ports?
Networks?
Services?
A better name is:
@management_networks Then the rule provides the policy:
ip saddr @management_networks tcp dport 22 accept The set describes what the data represents.
The rule describes what is allowed.
A firewall may have excellent IPv4 sets while leaving IPv6 behavior inconsistent.
If the server has IPv6 connectivity, define and review IPv6 policy explicitly.
Do not assume:
IPv4 blocked means:
service completely blocked in a dual-stack environment.
If a rule does not match, the real problem may be:
routing
wrong interface
NAT transformation
conntrack state
earlier firewall rule
IPv4/IPv6 mismatch
wrong port
asymmetric return path The set is only one component of the packet path.
If a set controls:
SSH administrators
VPN clients
DNS servers
critical application access flushing it can immediately disrupt production traffic.
Inspect first.
Change the smallest possible object.
Retest.
Dynamic state requires lifecycle management.
If entries should be temporary, design an appropriate timeout strategy.
Otherwise a dynamic set can gradually become:
large
stale
misleading and no longer represent the condition it was intended to track.
A well-structured nftables firewall can separate several concepts:
SETS
↓
What objects exist?
MAPS
↓
How keys map to values/actions?
CONNTRACK
↓
What state is the flow in?
RULES
↓
What policy applies?
COUNTERS
↓
How much traffic matches?
LOGGING
↓
What important events occurred?
VERDICT
↓
ACCEPT / DROP / JUMP This is significantly easier to manage than a giant flat list of individual rules.
A simplified architecture might look like:
table inet firewall {
set management_hosts {
type ipv4_addr
elements = {
192.168.30.10,
192.168.30.11
}
}
set web_servers {
type ipv4_addr
elements = {
192.168.20.10,
192.168.20.11
}
}
set web_ports {
type inet_service
elements = {
80,
443
}
}
chain forward {
type filter hook forward priority filter;
policy drop;
ct state invalid counter drop
ct state established,related counter accept
ip saddr @management_hosts \
ip daddr @web_servers \
tcp dport 22 \
ct state new \
counter \
accept
ip daddr @web_servers \
tcp dport @web_ports \
ct state new \
counter \
accept
}
} The policy is immediately understandable:
Established connections
↓
ACCEPT
Management hosts
↓
Web servers
↓
SSH
↓
ACCEPT
Approved web traffic
↓
Web servers
↓
ACCEPT
Everything else
↓
DROP That is much easier to maintain than dozens of repeated rules.
Use a set when you have a collection such as:
trusted IP addresses
network prefixes
service ports
server addresses
blocked sources
management networks and need to test membership.
Think:
Does X belong to this group? Use a map when you need:
key → value rather than simple membership.
Examples can include mappings used for NAT or other policy data.
Think:
Given X, what value corresponds to it? Use a verdict map when the key should lead directly to firewall control flow or a verdict.
Think:
Given this port/address/key,
what firewall action or chain should handle it? This can be extremely powerful in large modular rulesets.
Use concatenations when one packet attribute is insufficient.
For example:
source IP + port or more complex supported combinations.
Think:
Does this exact combination belong to the policy? This is useful for access-control matrices where neither the source nor the service should be trusted independently.
Use dynamic sets when membership should change based on runtime events rather than only administrator configuration.
Typical concepts include:
temporary tracking
temporary blocking
per-source state
time-based membership But dynamic behavior should be designed carefully because it introduces state into the firewall beyond normal connection tracking.
An nftables set is a collection of values that can be referenced by firewall rules.
Instead of creating separate rules for every address or port, multiple values can be grouped and matched through a set.
A set primarily represents membership:
key → present/not present A map associates:
key → value This makes maps useful when different keys should produce different results.
An anonymous set is defined directly inside a rule.
Example:
tcp dport { 22, 80, 443 } accept It is useful for small lists that do not need independent management.
A named set is created as a separate object and referenced using its name.
For example:
@trusted_admins Named sets are useful when values need to be reused, updated, or managed independently of firewall rules.
Yes.
For IPv4 addresses, sets can use an appropriate address datatype such as:
type ipv4_addr IPv6 sets can use:
type ipv6_addr Yes.
For service ports, a set can use:
type inet_service and contain values such as:
22
80
443 Interval-capable sets can represent ranges and prefixes, making them useful for subnet-based policies.
A verdict map maps keys to firewall verdicts or control-flow decisions.
Conceptually:
key
↓
lookup
↓
accept/drop/jump This can replace long groups of repetitive dispatch rules.
Sets and maps are specifically part of nftables’ scalable lookup architecture. The Netfilter project notes that long linear ruleset inspection does not scale well and highlights maps and concatenations as high-performance features.
Actual performance still depends on the complete ruleset, traffic pattern, kernel, hardware, and implementation.
Yes.
That is one of the major benefits of named sets.
You can modify set membership while leaving the rule referencing the set unchanged.
Sets can support timeout-oriented behavior where appropriate, making them useful for temporary dynamic state.
Yes, particularly for defining trusted management addresses.
Dynamic keyed state can also participate in more advanced per-source controls, although this should not replace strong SSH authentication and service hardening.
Start by verifying:
set exists
expected element exists
packet source/destination is what you expect
correct IP family is used
correct chain sees the packet
NAT has not changed the value
earlier rules are not handling the traffic Then use counters, tcpdump, conntrack, and targeted tracing.
When a set- or map-based rule behaves unexpectedly, check:
□ Is the correct table loaded?
□ Does the set exist?
□ Does the expected element exist?
□ Is the datatype correct?
□ Is the packet IPv4 or IPv6?
□ Is the actual source address what you expect?
□ Is the destination address what you expect?
□ Has DNAT or SNAT changed an address?
□ Is the destination port correct?
□ Does the expected rule counter increase?
□ Is another rule matching first?
□ What is the conntrack state?
□ Does tcpdump see the packet?
□ Does Linux use the expected route?
□ Is the return route correct?
□ Would targeted nftables tracing reveal the rule path? Do not change the entire firewall before answering these questions.
nftables sets and maps are among the most important tools for building scalable Linux firewall rulesets.
A basic firewall often begins like this:
IP A → rule
IP B → rule
IP C → rule
Port A → rule
Port B → rule
Port C → rule That approach can work for a small system, but it becomes increasingly difficult to manage as the network grows.
Sets allow the architecture to become:
Addresses
↓
SET
↓
Policy Maps extend that concept:
Key
↓
MAP
↓
Value Verdict maps can take it further:
Key
↓
VMAP
↓
Firewall action and concatenations allow several packet attributes to participate in the same lookup:
Source
+
Destination
+
Port
↓
Policy lookup The official Netfilter nftables project describes nftables as the successor to the older iptables-family tools and specifically highlights maps and concatenations as mechanisms for reducing linear rule inspection. The broader Netfilter project also identifies sets as more flexible than the older ipset approach and maps as an extension of that concept.
For CorgiNetwork, this article should sit inside a larger troubleshooting and firewall architecture cluster.
Use nftables Conntrack States and Troubleshooting when the problem involves NEW, ESTABLISHED, RELATED, or INVALID state.
Use Linux nftables Inter-VLAN Firewall when sets represent security zones or VLAN networks.
Use Linux DMZ with nftables when sets represent DMZ servers, management hosts, or public services.
Use nftables DNAT Port Forwarding Not Working when map- or set-based NAT rules do not behave as expected.
Use nftables Hairpin NAT and NAT Loopback when internal clients cannot reach a published internal service through its public address.
Use Linux Policy Routing with ip rule and Multiple Routing Tables when the firewall policy matches correctly but Linux selects the wrong route.
Use Linux rp_filter and Asymmetric Routing when stateful filtering breaks because request and reply paths are different.
Use Linux Traffic Uses the Wrong Interface when the kernel selects an unexpected NIC.
And use Linux ARP Flux with Multiple NICs on the Same Subnet when the apparent firewall problem is actually caused by Layer 2 behavior.
The complete CorgiNetwork learning path now becomes:
Linux Routing
↓
Policy Routing
↓
Multiple Interfaces
↓
ARP / rp_filter
↓
Network Namespaces
↓
VLAN Routing
↓
Inter-VLAN Firewall
↓
DMZ
↓
DNAT
↓
Hairpin NAT
↓
Conntrack
↓
Logging & Rate Limiting
↓
Sets & Maps
↓
Dynamic Sets & Meters
↓
Per-Source Rate Limiting
↓
Advanced nftables Firewall Architecture The principle to remember is simple:
Use rules to describe policy, sets to describe groups, maps to describe relationships, counters to measure behavior, and logs to explain important events.
That separation is what transforms a growing collection of nftables commands into a firewall architecture that can actually be maintained.
AI Agents as Code: Managing AI Agent Configurations, Prompts and Tools with Git AI agents…
Data Center Power Distribution Architecture: UPS, PDU, Redundancy and Rack Power Explained Data center power…
DNSSEC Troubleshooting: Fix SERVFAIL & DNSKEY Errors DNSSEC Troubleshooting: How to Fix SERVFAIL, DS, DNSKEY…
Terraform State Management Explained: Remote State, Locking, Backends and Best Practices Terraform makes infrastructure reproducible…
RTO vs RPO Explained: Disaster Recovery Objectives, Examples and Planning A disaster recovery plan is…
Network Intrusion Detection Explained: NIDS, Sensors, Signatures and Traffic Analysis A firewall can decide whether…