nftables Dynamic Sets and Meters: Per-Source Rate Limiting and Temporary Blocking
A basic nftables firewall can decide whether traffic should be accepted or dropped. A more advanced firewall can also remember temporary information about traffic, track individual sources, apply per-source rate limits, and automatically remove temporary entries when they expire.
This is where nftables dynamic sets, timeouts, meters, and per-source rate limiting become particularly useful.
Consider an Internet-facing SSH server.
A simple firewall might contain:
tcp dport 22 accept
A more restrictive firewall could limit access to a management network:
ip saddr 192.168.30.0/24 tcp dport 22 accept
But another problem remains: what happens when one permitted or exposed source generates an unusually large number of new connection attempts?
A simple global rate limit such as:
tcp dport 22 ct state new limit rate 10/minute accept
does not necessarily represent the policy many administrators actually want.
The desired logic may instead be:
Source A → its own rate
Source B → its own rate
Source C → its own rate
rather than:
A + B + C
↓
one shared rate
Dynamic sets and keyed rate-tracking mechanisms make this kind of policy possible.
They also enable temporary state such as:
Source detected
↓
Add to dynamic set
↓
Apply temporary policy
↓
Timeout expires
↓
Entry disappears
This article builds directly on nftables Sets and Maps and the concepts introduced in our nftables logging and rate-limiting guide.
It also assumes familiarity with connection tracking. If ct state new, established, related, and invalid are unfamiliar, read nftables Conntrack States and Troubleshooting first.
The nftables project is maintained as part of the broader Netfilter project, and the official nftables wiki is an important reference when validating syntax against the nftables version installed on a particular Linux distribution.
What Are nftables Dynamic Sets?
A normal nftables set contains values defined by the administrator.
For example:
set trusted_admins {
type ipv4_addr
elements = {
192.168.30.10,
192.168.30.20
}
}
The firewall configuration determines membership.
Conceptually:
Administrator
↓
Configuration
↓
@trusted_admins
A dynamic set allows elements to be added or updated as packets are processed.
Conceptually:
Packet
↓
Extract source address
↓
Add/update element
↓
Dynamic set
↓
Temporary state
This changes what a firewall can do.
Instead of knowing only:
Is this address on my static list?
the firewall can maintain temporary information associated with traffic it has observed.
Static Sets vs Dynamic Sets
The distinction is important.
A static set commonly represents relatively stable configuration:
@management_hosts
@dns_servers
@web_servers
@internal_networks
@allowed_services
These objects describe your network architecture.
Dynamic sets can instead represent runtime state such as:
recent_sources
temporary_addresses
sources being tracked
short-lived firewall state
A useful mental model is:
STATIC SET
↓
What the administrator knows
DYNAMIC SET
↓
What the firewall learns or tracks temporarily
Both belong to nftables, but they solve different problems.
Why Dynamic Firewall State Is Useful
Suppose a public server receives new SSH connections from:
198.51.100.10
198.51.100.20
198.51.100.30
A static firewall can evaluate each packet independently.
But suppose one source suddenly produces:
100 new connection attempts
while the others produce only one or two.
The security question is no longer simply:
Is TCP port 22 allowed?
It becomes:
How frequently is this particular source creating new connections?
That requires state associated with a key such as:
source IP
The resulting model becomes:
Packet
↓
Source IP
↓
Per-source state
↓
Rate evaluation
↓
Policy
This is fundamentally different from a single global rate limiter.
Global Rate Limiting vs Per-Source Rate Limiting
This distinction is one of the most important concepts in this article.
Consider three clients:
Client A
Client B
Client C
With a shared global limiter:
A ─┐
B ─┼→ Shared rate limit
C ─┘
Traffic from one client can contribute to the same overall limit as traffic from another.
But a per-source architecture behaves conceptually like:
Client A → limiter A
Client B → limiter B
Client C → limiter C
Each key maintains independent state.
For services such as SSH, APIs, VPN entry points, or other exposed services, this difference can be critical.
Why a Simple limit rate Is Not Always Enough
A rule such as:
limit rate 10/minute
is useful when the desired policy concerns the rate at which that rule as a whole matches.
For example, it is excellent for controlling log volume:
limit rate 5/second log prefix "NFT_DROP: "
because the objective is:
Do not generate unlimited log messages.
But if the objective is:
Track each source independently.
then the source address needs to participate in the state key.
This is where dynamic keyed tracking becomes relevant.
The official nftables documentation discusses the limit expression and related rate-limiting capabilities in the Rate limiting matchings guide.
What Is an nftables Meter?
Historically, nftables documentation and examples have used the term meter for dynamic state associated with packet-derived keys.
Conceptually, a meter combines:
packet key
+
dynamic state
+
rate evaluation
For example:
Source IP
↓
individual rate state
↓
threshold
The practical idea matters more than memorizing terminology:
Instead of applying one rate to every packet collectively, maintain rate state indexed by something such as the source address.
This allows:
192.0.2.10 → own state
192.0.2.20 → own state
192.0.2.30 → own state
The nftables syntax available for implementing these policies has evolved over time, so production rules should always be checked against the nftables and kernel versions installed on the target host rather than copied blindly from an old example.
Understanding the Key
The key determines what receives independent state.
For a common per-client policy, the key is:
ip saddr
which means:
source IPv4 address
Conceptually:
Packet
↓
ip saddr
↓
198.51.100.50
↓
state for 198.51.100.50
Another packet from:
198.51.100.60
uses different state.
That is the foundation of per-source rate limiting.
Why ct state new Matters
Suppose an SSH session transfers thousands of packets.
You usually do not want every packet in the established connection to contribute to a connection-attempt limiter.
The event of interest is generally:
NEW CONNECTION
not:
EVERY PACKET
Therefore a rate-control policy commonly needs to distinguish:
ct state new
from:
ct state established,related
Conceptually:
Packet
↓
ESTABLISHED / RELATED?
├── YES → normal established policy
│
└── NO
↓
NEW?
↓
rate-control logic
This is one reason connection tracking is central to advanced nftables firewall design.
Our nftables Conntrack States and Troubleshooting guide explains these states in detail.
Per-Source SSH Rate Limiting
SSH is a useful example because it illustrates the difference between packet rate and connection-attempt rate.
The desired policy might be:
Source IP
↓
NEW TCP/22 connection
↓
Track source
↓
Is source within allowed rate?
├── YES → continue
└── NO → apply excess-traffic policy
Notice what is not being counted:
every packet inside an established SSH session
This distinction prevents normal interactive traffic from being mistaken for repeated connection attempts.
Do Not Treat Rate Limiting as SSH Authentication
Firewall rate limiting can reduce unwanted connection pressure.
It does not replace:
SSH public-key authentication
strong account security
appropriate sshd configuration
software updates
administrative access controls
monitoring
An attacker staying below a firewall threshold may still attempt authentication.
The firewall controls network traffic.
The SSH service still needs to secure authentication.
Restrict SSH Before Rate Limiting It
If SSH should only be reachable from:
Management VLAN
VPN
specific administration hosts
that restriction is usually more important than exposing SSH broadly and relying on a rate limiter.
For example:
ip saddr @management_hosts \
tcp dport 22 \
ct state new \
accept
can be a stronger architectural control than:
Internet
↓
SSH available to everyone
↓
rate limiter
Sets and rate controls solve different problems.
Use both when appropriate.
Dynamic Sets and Timeouts
Dynamic state becomes far more useful when entries can expire.
Imagine a source is added to a set.
Without a timeout:
Source
↓
Set
↓
stays until explicitly removed
With a timeout:
Source
↓
Set
↓
Timer
↓
Expiration
↓
Removed
This makes temporary firewall state practical.
Why nftables Timeouts Matter
Suppose a firewall tracks addresses for 60 seconds.
The lifecycle becomes:
First relevant packet
↓
Create element
↓
Track source
↓
60 seconds
↓
Expire
Without expiration, temporary state can become permanent state accidentally.
Over time, that can lead to:
stale entries
large sets
unexpected policy
difficult troubleshooting
Timeouts provide a defined lifecycle.
Example of a Set with a Timeout
A set can be designed with timeout capability, conceptually like:
set recent_sources {
type ipv4_addr
flags timeout
timeout 1m
}
This expresses the idea that elements in the set are temporary rather than permanent configuration.
The exact update/add logic depends on the policy you build around the set.
The official nftables wiki’s Element timeouts guide provides additional background on timed set elements.
Per-Element Timeouts
A useful feature of timed sets is that individual elements can have their own lifetime.
Conceptually:
198.51.100.10
timeout 30s
198.51.100.20
timeout 5m
This allows different temporary states to coexist.
For most firewall designs, however, consistency is easier to maintain when a set has a clear purpose and predictable expiration policy.
Timeout vs Permanent Blocklist
Do not confuse:
temporary runtime state
with:
permanent administrative blocklist
A permanent blocklist might be:
set blocked_hosts {
type ipv4_addr
elements = {
198.51.100.10,
198.51.100.20
}
}
A temporary set might instead represent:
recently detected sources
with expiration.
Keeping these concepts separate improves troubleshooting.
Temporary Blocking Architecture
A temporary blocking design can be understood as several stages:
Traffic
↓
Detection condition
↓
Source identified
↓
Temporary set
↓
Blocking policy
↓
Timeout
↓
Automatic removal
This can be useful when you want temporary enforcement rather than permanent configuration changes.
However, the detection condition is critical.
A poorly designed threshold can block legitimate clients.
Rate Limiting Is Not the Same as Temporary Blocking
These two mechanisms are related but different.
Rate limiting
controls how frequently traffic is allowed to match or proceed.
Traffic
↓
Rate
↓
Within threshold?
Temporary blocking
changes the source’s policy for a period.
Condition triggered
↓
Source placed in temporary state
↓
Traffic blocked
↓
Timeout
A firewall can use one without the other.
Use Separate Rules for Detection and Enforcement
One of the best ways to keep advanced nftables policies understandable is to separate:
DETECTION
from:
ENFORCEMENT
For example:
Rule group A
↓
identify excessive behavior
Temporary set
↓
stores relevant source
Rule group B
↓
enforces temporary policy
This is much easier to troubleshoot than one extremely complex expression performing every function at once.
Add Logging to Temporary Blocks Carefully
If a source is generating excessive traffic, logging every packet from that source defeats part of the purpose of controlling it.
Instead:
Excess traffic
↓
rate-limited security log
↓
temporary enforcement
A conceptual logging rule might use:
limit rate 5/minute \
log prefix "NFT_RATE_EXCEEDED: "
while another rule handles the actual enforcement.
This separation ensures:
logging threshold
≠
security verdict
That principle is extremely important.
Never Accidentally Rate-Limit the Drop
Suppose your intended policy is:
Log 5 events per minute
Drop every prohibited packet
You do not want:
Drop only 5 prohibited packets per minute
A safer conceptual design is:
limit rate 5/minute \
log prefix "NFT_TEMP_BLOCK: "
drop
with the actual implementation structured so that the rate limiter controls logging rather than whether prohibited traffic receives the intended verdict.
Always test the complete rule path.
Counters Before Logs
When building dynamic rate-control rules, add counters.
For example:
counter recent_sources
counter excessive_sources
counter temporary_drop
Conceptually, counters answer:
How often does this stage match?
before you generate large volumes of logging.
This gives you a progression:
Counter
↓
Is the rule active?
Logging
↓
What traffic is matching?
Trace
↓
Why is it taking this path?
This approach is safer for production systems.
Dynamic Sets and nftables Logging
A strong observability architecture can look like:
Packet
↓
Conntrack state
↓
Dynamic source tracking
↓
Rate decision
↓
Counter
↓
Rate-limited log
↓
Verdict
Each component has a distinct role.
Conntrack identifies connection state.
Dynamic state tracks the relevant key.
Rate logic evaluates behavior.
Counter measures activity.
Logging records selected events.
Verdict enforces policy.
Keeping these responsibilities separate makes complex firewalls far easier to understand.
Dynamic Sets and Maps
The previous nftables Sets and Maps article introduced:
SET
key → membership
and:
MAP
key → value
Dynamic sets extend the first concept by making membership change while traffic is being processed.
Conceptually:
STATIC SET
Administrator
↓
membership
DYNAMIC SET
Traffic
↓
membership/state
Maps and verdict maps can then be used elsewhere to make structured policy decisions.
Combining Static and Dynamic Sets
Production firewalls do not have to choose between them.
You might have:
@management_hosts
↓
static
@web_servers
↓
static
@temporary_sources
↓
dynamic
@blocked_sources
↓
administrative/static
The firewall can then distinguish trusted architecture from runtime state.
For example:
Management source?
↓
trusted policy
Temporary blocked source?
↓
temporary enforcement
Other source?
↓
normal public policy
This is much cleaner than placing every address into one giant set.
Use Descriptive Set Names
Avoid:
@set1
@dynamic
@block2
@temp
Prefer names such as:
@management_hosts
@recent_ssh_sources
@temporary_ssh_blocks
@trusted_api_clients
@temporary_api_blocks
A good name should tell another administrator what the set represents without requiring them to reverse-engineer the entire ruleset.
Per-Source Limits for APIs
The same concept used for SSH can be applied to an exposed API.
Imagine:
Internet
↓
TCP/443
↓
API server
The firewall may track connection-level behavior by source.
But remember that nftables operates primarily at the network/transport firewall layer.
It generally cannot understand application concepts such as:
API account
authenticated user
HTTP endpoint cost
API token
business transaction
A client may also appear behind:
NAT
corporate proxy
carrier-grade NAT
where many users share one source address.
Therefore network-layer per-IP limiting should complement—not replace—application-level rate limiting.
NAT Can Change What “Per Source” Means
Suppose hundreds of users are behind one NAT gateway:
User A ─┐
User B ─┤
User C ─┼→ NAT → 198.51.100.50
User D ─┘
Your firewall sees:
198.51.100.50
as the source.
A per-source limiter may therefore treat all those users as one client.
This is an important design limitation.
The firewall can only make decisions using information available at the hook where the rule runs.
Be Careful Behind Reverse Proxies
The same principle applies when a service sits behind a reverse proxy or another network intermediary.
The firewall may see the proxy’s address rather than the original application client.
Do not assume:
ip saddr
always means:
individual human user
It means the source address visible to the firewall at that point in packet processing.
Dynamic Rate Limiting in a DMZ
Consider a CorgiNetwork-style DMZ architecture:
Internet
↓
WAN Firewall
↓
DMZ Web Server
↓
Internal Network
Static sets can define:
@dmz_web_servers
@management_hosts
@public_services
while dynamic state can track selected public-facing behavior.
The design becomes:
STATIC DATA
↓
What servers and networks exist?
CONNTRACK
↓
What connection state exists?
DYNAMIC STATE
↓
What recent behavior has occurred?
POLICY
↓
What should happen?
For the underlying segmentation architecture, see Linux DMZ with nftables.
Dynamic State in Inter-VLAN Firewalls
Dynamic controls can also be used internally.
Consider:
VLAN 10 → Users
VLAN 20 → Servers
VLAN 30 → Management
VLAN 40 → IoT
Static policy should first define what communication is legitimate.
For example:
Users
↓
HTTPS
↓
Servers
and:
Management
↓
SSH
↓
Servers
Dynamic rate controls can then complement those rules.
Do not use rate limiting as a substitute for segmentation.
If an IoT VLAN should never SSH to the server VLAN, the correct policy is generally:
DROP
not:
allow some SSH attempts per minute
For the segmentation model, see Linux nftables Inter-VLAN Firewall.
Dynamic Sets and DNAT
Suppose a public address forwards SSH to an internal host:
Internet
↓
203.0.113.10:2222
↓
DNAT
↓
192.168.20.10:22
When adding dynamic tracking, you must understand where the rule executes.
At different stages, the packet may have:
original destination
or:
translated destination
while conntrack retains information about the flow.
A rate-control rule placed at the wrong hook can therefore match something different from what you expect.
For NAT packet-path troubleshooting, use nftables DNAT Port Forwarding Not Working.
Dynamic Sets and Hairpin NAT
Hairpin NAT creates another special path:
Internal Client
↓
Public IP
↓
DNAT
↓
Internal Server
Depending on the topology, source translation may also be involved.
If a dynamic policy behaves differently for internal and external clients, first establish:
which source address is visible
which destination is visible
which hook sees the packet
what conntrack state exists
before modifying the rate limiter.
See nftables Hairpin NAT and NAT Loopback for the full packet-path explanation.
Routing Problems Can Look Like Rate-Limit Problems
Suppose the firewall accepts the connection but the reply leaves through the wrong interface.
The user still sees:
connection timeout
It is easy to assume:
rate limiter blocked it
when the real problem is:
routing
If the machine has multiple gateways, see Linux Policy Routing with ip rule and Multiple Routing Tables.
If Linux chooses the wrong interface, see Linux Traffic Uses the Wrong Interface.
Asymmetric Routing and Conntrack
Dynamic firewall logic often relies on conntrack.
Therefore asymmetric routing deserves particular attention.
Consider:
Request
↓
eth0
↓
Firewall
Reply
↓
eth1
↓
Client
Depending on the complete topology and configuration, asymmetric paths can complicate:
conntrack
rp_filter
stateful firewall policy
troubleshooting
If packets unexpectedly become INVALID or replies disappear, investigate routing before weakening firewall policy.
See Linux rp_filter and Asymmetric Routing.
ARP Flux Can Also Mislead You
On multi-homed Linux systems where multiple interfaces share a subnet, ARP behavior can make traffic appear on an unexpected interface.
You might spend hours debugging:
dynamic set
rate limiter
firewall chain
when the underlying issue is Layer 2 address resolution.
For this case, see Linux ARP Flux with Multiple NICs on the Same Subnet.
Troubleshooting Per-Source Rate Limiting
When legitimate traffic is unexpectedly blocked, do not immediately raise the threshold.
First identify the real behavior.
Start with:
sudo nft list ruleset
Verify:
set definition
timeout
rule order
matching expressions
counter values
Then capture traffic:
sudo tcpdump -ni any tcp port 22
Inspect connection state when appropriate:
sudo conntrack -L -p tcp
and examine carefully scoped firewall logs.
You want to answer:
What source does nftables actually see?
How many NEW connections are occurring?
Is the client reconnecting automatically?
Is NAT combining several users?
Is the packet matching an earlier rule?
Is the expected set entry present?
Has its timeout expired?
Only then should you change the threshold.
Inspect Dynamic Sets
During troubleshooting, inspect the live nftables state rather than only reading the configuration file.
The key questions are:
Is the source present?
When was it added?
Is the timeout behaving as expected?
Is the wrong address being tracked?
Are entries disappearing when expected?
Runtime state is especially important because the configuration file may define the mechanism without showing the current dynamic membership.
Add Counters at Each Stage
For difficult policies, counters can create a breadcrumb trail.
Conceptually:
NEW SSH traffic
counter A
↓
rate evaluation
counter B
↓
temporary state
counter C
↓
drop
counter D
Now you can determine exactly where traffic stops progressing.
For example:
A increases
B increases
C = 0
suggests a very different problem from:
A = 0
Counters reduce guesswork.
Use tcpdump to Verify the Traffic
A firewall rule cannot match a packet that never reaches the expected system or interface.
For SSH:
sudo tcpdump -ni any tcp port 22
For a particular client:
sudo tcpdump -ni any host 198.51.100.50 and tcp port 22
This tells you what packets are actually visible.
It does not tell you which nftables rule matched them.
That is why packet capture and firewall counters complement each other.
Use Conntrack to Understand Connections
If your limiter is based on:
ct state new
then conntrack matters.
The Linux kernel Netfilter documentation documents important conntrack-related kernel controls, while the conntrack userspace utility can help inspect tracked flows.
The key principle is:
Packet count
≠
connection count
A single TCP connection can contain many packets.
Do not design a connection-attempt policy as though every packet represented a new connection.
Use nftables Trace for Difficult Cases
When a packet crosses many chains, sets, maps, NAT rules, and stateful conditions, targeted tracing may be necessary.
The official nftables Ruleset tracing documentation describes the tracing mechanism used with:
sudo nft monitor trace
The objective is to follow a narrowly selected packet through the ruleset.
Do not trace all production traffic unnecessarily.
Rule Order Still Matters
Dynamic sets do not remove normal nftables rule-order considerations.
Suppose:
Rule 1
accept TCP/22
Rule 2
perform SSH rate-control logic
If the first rule accepts the traffic, the later logic may never see it.
Always inspect the complete chain:
top
↓
rule
↓
rule
↓
rate policy
↓
verdict
A perfectly written dynamic set cannot help if an earlier terminal verdict bypasses it.
Avoid Locking Yourself Out
SSH rate-control testing deserves particular caution.
Before changing a remote production firewall, ensure you have an appropriate recovery path such as:
hosting console
out-of-band management
physical console
tested alternate administration path
Do not replace the only working SSH rule with an untested advanced dynamic policy.
Test new rules carefully before making them persistent.
IPv4 and IPv6 Need Separate Consideration
A policy keyed on:
ip saddr
concerns IPv4.
If the service is also reachable through IPv6, you need to review the IPv6 path as well.
A server can otherwise end up with:
IPv4 → rate controlled
IPv6 → different policy
This creates inconsistent security behavior.
For dual-stack services, design both intentionally.
Beware of Shared Source Addresses
Per-IP controls have a fundamental limitation:
one IP
does not always equal:
one user
Possible shared-address environments include:
corporate NAT
carrier-grade NAT
VPN gateways
reverse proxies
shared hosting infrastructure
A threshold that works for individual residential clients may be inappropriate when hundreds of legitimate users share one address.
Do Not Use Extremely Aggressive Thresholds Without Measurement
Suppose you configure a very low limit because:
one connection per minute seems enough
But a legitimate system might:
reconnect after network changes
run monitoring checks
open parallel connections
retry failed connections
use automation
Measure normal behavior before choosing production thresholds.
Security controls should be based on the traffic model, not arbitrary numbers.
Dynamic Sets Are Not an IDS
Dynamic nftables rules can identify patterns that you define.
They do not automatically understand attacker intent.
A high connection rate can result from:
malicious scanning
misconfigured monitoring
application retry loops
load testing
legitimate high-volume clients
The firewall observes traffic characteristics.
It does not automatically determine why the traffic exists.
Dynamic Sets Are Not a WAF
Similarly, nftables cannot replace an application-aware Web Application Firewall.
At the network layer, it can evaluate information such as:
IP addresses
ports
protocols
connection state
packet metadata
It does not normally understand application semantics such as:
login username
shopping-cart behavior
API token ownership
SQL query intent
application authorization
Use the right security control at the right layer.
Dynamic Sets Are Not Full DDoS Protection
Per-source rate controls can be useful against some traffic patterns.
But distributed traffic can originate from:
thousands
or:
millions
of addresses.
Furthermore, if an upstream link is already saturated, a firewall on the destination server cannot recover bandwidth that has already been consumed before packets reach it.
Large-scale DDoS mitigation often requires upstream network capacity and provider-side controls.
Memory and State Matter
Dynamic firewall state consumes resources.
A design that tracks a tiny number of SSH sources is very different from one that attempts to create state for enormous numbers of Internet addresses.
Consider:
expected number of elements
element lifetime
traffic volume
memory constraints
garbage collection behavior
failure conditions
before deploying large dynamic structures.
Timeouts Should Match the Purpose
A timeout of:
10 seconds
might be appropriate for one kind of temporary observation.
A timeout of:
1 hour
might be appropriate for another.
There is no universal best timeout.
Choose it based on:
what event is being tracked
how quickly legitimate clients recover
how much state you want to retain
what operational effect expiration should have
Document the reason.
Separate Security Zones Before Adding Complexity
Suppose the network contains:
WAN
LAN
DMZ
MANAGEMENT
VPN
IoT
Start with explicit zone policy:
Who can talk to whom?
Then add dynamic controls where they provide additional value.
Do not build a sophisticated per-source rate limiter to compensate for a missing segmentation policy.
The firewall architecture should progress:
Routing
↓
Segmentation
↓
Stateful policy
↓
Static sets/maps
↓
Logging/counters
↓
Dynamic controls
not the other way around.
A Production Design Pattern
A simplified conceptual firewall might use:
table inet firewall {
set management_hosts {
type ipv4_addr
elements = {
192.168.30.10,
192.168.30.20
}
}
set temporary_sources {
type ipv4_addr
flags timeout
timeout 5m
}
chain input {
type filter hook input priority filter;
policy drop;
ct state invalid counter drop
ct state established,related counter accept
ip saddr @management_hosts \
tcp dport 22 \
ct state new \
counter \
accept
# Dynamic rate-control logic belongs here
# when SSH is intentionally exposed to other sources.
counter
}
}
This example intentionally emphasizes architecture rather than presenting an untested universal blocking recipe.
A production rule must be adapted to:
your nftables version
kernel version
IP family
interfaces
service exposure
normal traffic rates
recovery requirements
before deployment.
Better Firewall Architecture
As the CorgiNetwork nftables series develops, the overall architecture now looks like:
PACKET
↓
ROUTING
↓
CONNTRACK
↓
STATIC SETS / MAPS
↓
DYNAMIC STATE
↓
RATE EVALUATION
↓
COUNTERS
↓
SELECTIVE LOGGING
↓
VERDICT
Each layer answers a different question.
Routing
Where should the packet go?
Conntrack
What connection state does it belong to?
Static sets
What known group does the address/service belong to?
Dynamic state
What recent runtime state is associated with the source?
Rate evaluation
Is the observed behavior within policy?
Counters
How often is this path being used?
Logging
What important event should be recorded?
Verdict
Should the packet be accepted, dropped, rejected, or sent to another chain?
That separation makes advanced firewalls maintainable.
Common nftables Dynamic Set Mistakes
The first common mistake is confusing global and per-source rate limiting. If the policy says “each client,” the source needs to be part of the state model.
The second is counting every packet instead of new connections. For connection-attempt controls, ct state new is often the relevant event.
The third is forgetting timeouts. Temporary runtime state should have an intentional lifecycle.
The fourth is using rate limiting instead of access control. If a network should never access a service, deny it rather than merely limiting it.
The fifth is logging every excessive packet. Rate-limit security logs separately from the enforcement decision.
The sixth is assuming one IP equals one person. NAT, proxies, VPNs, and CGNAT can combine many users behind one address.
The seventh is ignoring IPv6. IPv4-only controls do not automatically protect an IPv6-exposed service.
The eighth is testing SSH without recovery access. A firewall mistake can terminate your only administrative path.
The ninth is copying old meter syntax blindly. nftables evolves; verify examples against your installed userspace and kernel.
The tenth is assuming the firewall is always the problem. Routing, NAT, ARP, rp_filter, and conntrack can produce similar symptoms.
Frequently Asked Questions
What is an nftables dynamic set?
A dynamic set is a set whose runtime membership can be populated or updated while packets are being processed rather than being limited to values statically defined by the administrator.
This allows nftables to maintain temporary packet-derived state.
What is an nftables meter?
The term meter has historically been used in nftables documentation for mechanisms that maintain dynamic state keyed by packet information and can apply rate-based evaluation.
A common conceptual example is maintaining separate rate state for each source IP.
What is the difference between global and per-source rate limiting?
A global rate limiter applies shared rate state to traffic matching the rule.
A per-source limiter maintains independent state based on a key such as the source address.
Conceptually:
GLOBAL
A + B + C → one limit
versus:
PER SOURCE
A → limit A
B → limit B
C → limit C
Can nftables temporarily block an IP address?
Yes. Dynamic set mechanisms combined with timed elements can be used to maintain temporary source state that expires automatically.
The exact enforcement logic should be designed and tested carefully.
Can nftables set entries expire automatically?
Yes. Sets can support timeout-oriented behavior, allowing elements to expire after a configured period.
This is useful for temporary runtime state.
Can nftables rate-limit SSH?
Yes.
A useful design usually focuses on new SSH connections rather than every packet belonging to an established SSH session.
However, rate limiting should complement SSH authentication and network access controls rather than replace them.
Should I rate-limit SSH globally or per IP?
That depends on the intended policy.
If you want one total connection rate for the entire service, global limiting may be appropriate.
If the requirement is that each source receive independent state, use a per-source design.
Remember that multiple legitimate users may share one public address.
Should I log every blocked source?
Usually not every packet.
Use counters for volume and rate-limited logging for significant events.
Unlimited logging during high traffic can create excessive log volume and obscure useful information.
Can dynamic sets replace Fail2ban?
They can implement some network-level temporary source-control patterns, but the tools operate at different layers and can use different signals.
A system that analyzes application authentication logs has information that a network firewall may not possess.
Choose the mechanism based on what event you actually need to detect.
Can dynamic nftables rules stop DDoS attacks?
They can help control some traffic patterns, but they are not a complete DDoS solution.
Large distributed attacks may require upstream mitigation because the destination firewall cannot recover bandwidth already saturated before traffic reaches it.
Why is a legitimate client hitting my per-source limit?
Possible causes include:
multiple users behind NAT
application reconnect loops
monitoring systems
parallel connections
very low threshold
VPN gateway sharing
connection failures causing retries
Measure actual traffic before changing the policy.
Why does my dynamic set contain an unexpected IP?
Verify the address visible at the exact firewall hook where the rule runs.
NAT, proxies, network topology, or the selected key may make the observed source different from what you expected.
How do I troubleshoot nftables dynamic sets?
Use a layered approach:
routing
↓
tcpdump
↓
ruleset inspection
↓
set contents
↓
counters
↓
conntrack
↓
rate-limited logs
↓
targeted nft trace
Do not start by randomly changing thresholds.
Troubleshooting Checklist
Before modifying a production dynamic rate-control policy, verify:
□ Is the packet reaching the expected interface?
□ What source address does the firewall actually see?
□ Is NAT involved?
□ Is the service IPv4, IPv6, or both?
□ Is the rule evaluating ct state new?
□ Does an earlier rule accept or drop the traffic?
□ Is the dynamic set populated?
□ Is the expected source inside it?
□ Is the timeout behaving correctly?
□ Does the relevant counter increase?
□ Are several legitimate users sharing one IP?
□ Is the application reconnecting repeatedly?
□ Does conntrack show the expected flow?
□ Is the return route correct?
□ Is asymmetric routing involved?
□ Are logs rate-limited independently?
□ Would targeted nftables tracing identify the exact rule path?
This checklist helps separate a genuine rate-control problem from routing, NAT, connection tracking, or application behavior.
Conclusion
nftables dynamic sets and meters extend Linux firewalling from static packet filtering into temporary, keyed runtime state and per-source traffic control.
A traditional firewall asks:
Does this packet match?
Static sets improve that question:
Does this packet belong to a known group?
Dynamic state adds another dimension:
What has this source been doing recently?
Per-source rate limiting then asks:
Is this individual source operating within the defined rate?
The complete model becomes:
PACKET
↓
ROUTING
↓
CONNTRACK
↓
STATIC POLICY
↓
┌────────┴────────┐
↓ ↓
SETS MAPS
↓ ↓
└────────┬────────┘
↓
DYNAMIC STATE
↓
SOURCE / KEY
↓
RATE EVALUATION
↓
┌────────┴────────┐
↓ ↓
COUNTER SELECTIVE LOG
↓ ↓
└────────┬────────┘
↓
VERDICT
For the underlying data structures, continue using nftables Sets and Maps.
When connection state is unclear, use nftables Conntrack States and Troubleshooting.
For segmented networks, combine these controls with Linux nftables Inter-VLAN Firewall and Linux DMZ with nftables.
For published services, use nftables DNAT Port Forwarding Not Working and nftables Hairpin NAT and NAT Loopback.
When the firewall looks correct but traffic follows the wrong path, continue with Linux Policy Routing with ip rule and Multiple Routing Tables, Linux Traffic Uses the Wrong Interface, and Linux rp_filter and Asymmetric Routing.
For multi-NIC Layer 2 problems, use Linux ARP Flux with Multiple NICs on the Same Subnet.
The CorgiNetwork technical cluster now progresses naturally through:
Linux Routing
↓
Policy Routing
↓
Multiple Interfaces
↓
ARP Flux / rp_filter
↓
Network Namespaces
↓
VLAN Routing
↓
Inter-VLAN Firewall
↓
DMZ
↓
DNAT
↓
Hairpin NAT
↓
Conntrack
↓
Logging & Rate Limiting
↓
Sets & Maps
↓
Dynamic Sets & Meters
↓
Per-Source Traffic Control
↓
Advanced nftables Architecture
The principle to remember is:
Use static sets to describe what you know, dynamic sets to represent temporary state, conntrack to understand connections, counters to measure behavior, and rate controls to enforce carefully defined traffic policies.
A firewall becomes truly maintainable when each mechanism has one clear responsibility.