An nftables set is a named collection of addresses, ports or other values that one rule can match in a single fast lookup. Sets replace long chains of near-identical rules, and you can add or remove elements while the firewall runs. This guide adds three sets to a basic server ruleset: an SSH allowlist, a blocklist with CIDR ranges and temporary bans, and a dynamic set that rate-limits new SSH connections per source IP.
Before you start
- A Linux VPS managed with plain nftables, not UFW or firewalld. If you have not built a ruleset yet, start with Get started with nftables.
- Root or a sudo user. Ubuntu 22.04 or 24.04, Debian 12, or AlmaLinux or Rocky Linux 9 have recent enough nftables for every example here.
- Your own public IP. Examples use
203.0.113.10. - Access to the browser console in your VPS provider's control panel.
Warning: An SSH allowlist blocks every address not in the set, including your home IP if it changes. Keep your SSH allow rule in place until you have tested the new one from a second session, and remember the browser console in your VPS provider's control panel works even when SSH is blocked.
Set basics
A set declaration names a type and optional flags:
type ipv4_addroripv6_addrfor addresses,inet_servicefor ports.flags intervallets the set hold CIDR ranges such as198.51.100.0/24.flags timeoutlets each element expire on its own.flags dynamiclets rules add elements from the packet path.auto-mergejoins overlapping or adjacent ranges instead of rejecting them.
A rule refers to a named set with @name, for example ip saddr @blocklist drop.
Add the sets to your ruleset
Edit your ruleset file (/etc/nftables.conf on Ubuntu and Debian, /etc/sysconfig/nftables.conf on AlmaLinux and Rocky Linux). This version extends the basic input chain:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set ssh_allow {
type ipv4_addr
flags interval
auto-merge
elements = { 203.0.113.10, 198.51.100.0/28 }
}
set blocklist {
type ipv4_addr
flags interval
auto-merge
}
set tempban {
type ipv4_addr
flags timeout
timeout 1h
}
set ssh_meter {
type ipv4_addr
size 65535
flags dynamic, timeout
timeout 10m
}
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif "lo" accept
ip saddr @blocklist drop
ip saddr @tempban drop
meta l4proto { icmp, ipv6-icmp } accept
tcp dport 22 ct state new ip saddr != @ssh_allow drop
tcp dport 22 ct state new update @ssh_meter { ip saddr limit rate over 10/minute burst 5 packets } add @tempban { ip saddr } drop
tcp dport 22 accept
tcp dport { 80, 443 } accept
}
chain forward {
type filter hook forward priority filter; policy drop;
}
}How it fits together:
- Blocklist and tempban are checked early, so listed addresses never reach any service.
- ssh_allow drops new SSH connections from anyone not on the list. Leave out that line if you want SSH open to everyone and rely on rate limiting only.
- ssh_meter keeps one rate counter per source IP. An address that opens more than 10 new SSH connections a minute, after a burst of 5, is added to
tempbanfor an hour. - Established connections are accepted first, so your current session is never cut by these rules.
Change 22 if SSH runs on another port. These sets cover IPv4 only: rules that match ip saddr ignore IPv6 packets, so SSH over IPv6 is not covered by the allowlist. If your server has IPv6, add matching ipv6_addr sets and rules using ip6 saddr.
Test and apply safely
Check the syntax first:
sudo nft -c -f /etc/nftables.confSchedule a rollback in case the allowlist is wrong, then load the file:
sudo systemd-run --on-active=5m --unit=nft-rollback /usr/sbin/nft flush ruleset
sudo nft -f /etc/nftables.confOpen a new SSH session from your allowed IP. If it works, cancel the rollback:
sudo systemctl stop nft-rollback.timerThe automatic rollback guide explains this pattern in more detail.
Add and remove elements live
Changes made with nft take effect at once, with no reload:
# block one address and a whole range
sudo nft add element inet filter blocklist { 198.51.100.7, 192.0.2.0/24 }
# ban an address for 30 minutes instead of the default hour
sudo nft add element inet filter tempban { 198.51.100.8 timeout 30m }
# allow a new admin IP for SSH
sudo nft add element inet filter ssh_allow { 203.0.113.25 }
# remove entries
sudo nft delete element inet filter blocklist { 198.51.100.7 }Live changes are lost when the ruleset file is reloaded. For permanent entries in ssh_allow or blocklist, add them to the elements line in the file too. tempban and ssh_meter are meant to be temporary, so they start empty on each load.
To load a long blocklist, keep it in a separate file and include it. For example, create /etc/nftables.d/blocklist.nft:
add element inet filter blocklist { 198.51.100.0/24, 192.0.2.0/25 }Then add include "/etc/nftables.d/blocklist.nft" at the end of the main file, after the table is defined.
Check it works
List a set and its current elements:
sudo nft list set inet filter blocklist
sudo nft list set inet filter tempbanElements in tempban show the time left before they expire. To test rate limiting, open many quick SSH connections from a test machine that is in ssh_allow but is not your main admin IP, then check that its address appears in tempban.
Troubleshooting
- "conflicting intervals specified": you added overlapping ranges to an interval set without
auto-merge. Addauto-mergeto the set and reload. - "Could not process rule" on the
update @ssh_meterline: your nftables or kernel is too old for per-element limits. Checknft --version, update the OS, or drop the rate-limit line and use Fail2ban instead. - Rate limiting bans everyone at once: the limit was written outside the set element, which makes it a single global counter. Keep
limit rateinside the braces afterip saddr. - Added IPs vanish after reboot: they were added live only. Put them in the ruleset or an included file.
- Locked out by the allowlist: log in through the browser console and run
sudo nft add element inet filter ssh_allow { your.new.ip }, then update the file.
Next steps
- Turn a firewalld server into the same design: Block thousands of IPs with firewalld ipsets.
- Use nftables for NAT and port forwards: NAT, masquerade and port forwarding with nftables.
