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.
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.
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.
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.
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.
limit rate Is Not Always EnoughA 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.
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.
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.
ct state new MattersSuppose 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.
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.
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.
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 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.
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.
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.
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.
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.
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.
These two mechanisms are related but different.
controls how frequently traffic is allowed to match or proceed.
Traffic
↓
Rate
↓
Within threshold? 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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.
tcpdump to Verify the TrafficA 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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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 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.
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.
Where should the packet go?
What connection state does it belong to?
What known group does the address/service belong to?
What recent runtime state is associated with the source?
Is the observed behavior within policy?
How often is this path being used?
What important event should be recorded?
Should the packet be accepted, dropped, rejected, or sent to another chain?
That separation makes advanced firewalls maintainable.
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.
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.
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.
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 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.
Yes. Sets can support timeout-oriented behavior, allowing elements to expire after a configured period.
This is useful for temporary runtime state.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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…