Why nftables?
If you’re still editing /etc/iptables/rules.v4 and rules.v6 separately in 2026, it’s time to move on. Debian 12 and Ubuntu 24.04 ship with nftables as the kernel firewall — iptables userspace tools are a compatibility wrapper on top of the nf_tables kernel API.
Check your own system:
|
|
Output:
iptables v1.8.10 (nf_tables)
That (nf_tables) means iptables commands are translated to nftables bytecode at runtime. You’re already using nftables under the hood — but via a translation layer that’s one more abstraction to debug.
Why switch to native nftables:
- Single ruleset for IPv4 and IPv6 — one file, one
inetfamily table, no duplicate rules - Atomic rule replacement —
nft -fapplies the entire ruleset atomically; no intermediate window with no rules - Native rate limiting — no external tools like fail2ban for simple rate-based blocking
- Named sets and maps — dynamic address lists, policy dispatch, all in-kernel
- Cleaner syntax — no arcane line numbering, no append/insert at position confusion
- Performance — single pass through the in-kernel bytecode VM
nftables Basics — Tables, Chains, Rules
nftables organizes rules in three layers: tables contain chains, which contain rules.
Tables and Address Families
Tables are scoped to an address family. The most useful for homelab servers:
| Family | Purpose |
|---|---|
inet |
IPv4 and IPv6 combined (default for host filtering) |
ip |
IPv4 only |
ip6 |
IPv6 only |
arp |
ARP filtering |
bridge |
Bridge port filtering |
netdev |
Ingress filtering at the lowest level |
Create a table:
|
|
Chains and Hooks
Chains define the hook point and processing pipeline:
|
|
Chain types: filter (most rules), nat (NAT rules), route (policy routing). Priorities determine evaluation order within the same hook — 0 is the default for filter chains, lower numbers run first.
Rules
Rules combine matches (packet criteria) with verdicts (actions):
|
|
Verdicts: accept, drop, reject, log, counter, jump (sub-chain), goto.
Ruleset Files — The Idiomatic Way
CLI commands are great for testing, but permanent configuration belongs in a file loaded with nft -f. The Debian convention is /etc/nftables.conf.
|
|
Writing a Practical Homelab Ruleset
Here’s a complete, production-ready /etc/nftables.conf for a homelab Linux server. This covers a typical machine running SSH, HTTP/HTTPS, Docker containers, and a WireGuard VPN.
|
|
Key points in this ruleset:
inetfamily — one table covers both IPv4 and IPv6ct state established,related accept— necessary in bothinputandforwardto keep ongoing connections alive- ICMPv6 needs more types than ICMPv4 because IPv6 Neighbor Discovery Protocol (NDP) is required for link-layer communication
ssh-bad-guysset tracks IPs that exceed the rate limit and blocks them automatically (see section 5)- Docker bridge rules in the
forwardchain — essential when you disable Docker’s iptables management
Docker + nftables — Making Them Work Together
Docker by default manipulates iptables rules to handle container networking. Since Debian 12 / Ubuntu 24.04 use the nftables kernel backend, Docker’s iptables calls are transparently translated — so it works out of the box.
The problem? If you flush the nftables ruleset with nft flush ruleset, Docker’s rules disappear too. And if your nftables ruleset includes a forward chain with policy drop, Docker containers lose external connectivity until you add explicit forward rules.
The Clean Solution: Disable Docker iptables Management
Edit /etc/docker/daemon.json:
|
|
|
|
Caution: With "iptables": false, Docker does not create any firewall rules. Your containers can still reach the internet through the Docker bridge, but only if your nftables forward chain explicitly allows it. The ruleset above includes docker0 rules that make this work.
Alternative: Let Docker Manage iptables and Don’t Flush
If you prefer minimal configuration, leave Docker’s default behavior alone. Your nftables ruleset must add rules rather than flush everything. Instead of flush ruleset, use nft -f to create new tables without affecting Docker’s internal rules. This approach is simpler but means you lose atomic ruleset replacement.
For most homelab setups, disabling Docker’s iptables and writing explicit forward rules is more predictable and auditable.
Rate Limiting SSH and Brute Force Protection
nftables handles rate limiting natively — no additional packages needed.
Static Rate Limit (Simple)
The simplest approach: limit SSH connections from any single source to 10 per minute with a burst of 5. Packets exceeding the limit are dropped:
|
|
Place this before any general SSH accept rule.
Dynamic Deny Set (Advanced)
For better brute force protection, use a dynamic set that adds offenders to a deny list. Once an IP exceeds the rate limit, it’s blocked for the lifetime of the set:
|
|
The first time an IP exceeds 3 connections per minute (burst allowed up to 5), it gets added to @ssh-bad-guys. The set has flags dynamic, which means matching entries are added at evaluation time. Subsequent packets from that IP hit the set and are dropped by the policy.
To view the current deny list:
|
|
To clear the deny list:
|
|
Logging Firewall Events
Logging is essential for debugging and security auditing. But naive logging floods syslog — a single SSH scan can generate thousands of log lines per second. Always rate-limit your log rules.
Logging with Rate Limits
|
|
This logs at most 5 packets per second (with a burst of 10), prefixing each log line with NFTABLES-INPUT-DROP: . The counter tracks how many packets hit this rule — useful for dashboards.
Viewing Logs
|
|
Dedicated Log File
Route nftables logs to a separate file via rsyslog. Create /etc/rsyslog.d/30-nftables.conf:
:msg, contains, "NFTABLES-INPUT-DROP" /var/log/nftables-input.log
:msg, contains, "NFTABLES-FORWARD-DROP" /var/log/nftables-forward.log
& stop
|
|
This also makes log scraping for Grafana/Loki straightforward — point Promtail at /var/log/nftables-*.log.
Rule Persistence with systemd
Debian and Ubuntu ship the nftables.service systemd unit. The default config file is /etc/nftables.conf.
Enable and Start
|
|
Check Status
|
|
The service loads the ruleset at boot. If there’s a syntax error, the service fails and you lose your firewall — always validate before reloading.
Atomic Reload Script
|
|
|
|
Reload Without Dropping Connections
|
|
nft -f replaces rules atomically without flushing connection tracking — existing established connections are unaffected.
Tools and Debugging
List the Full Ruleset
|
|
Watch it live as you make changes:
|
|
Packet Tracing with nft monitor
nft monitor shows packet traversal through the ruleset in real time:
|
|
You need to add a trace rule first:
|
|
Then packets to port 22 will show each chain and rule evaluated with the verdict reached.
tcpdump for Deep Inspection
nftables tells you what it did with a packet. tcpdump tells you what the packet contains:
|
|
Combine both when debugging connectivity issues — the firewall rule, and the actual traffic.
Common Pitfalls
Missing semicolons in chain declarations:
|
|
Semicolons in nft -f files are NOT escaped — the shell is not involved when reading from a file.
IPv6 scoped addresses: On multi-homed hosts with link-local IPv6, specify the zone ID explicitly:
|
|
Rule order: nftables evaluates rules in order. Put your most specific rules first, general rules last. The policy handles anything not explicitly matched.
Advanced: nftables Sets and Maps
Sets are one of nftables’ killer features — in-kernel lookup tables with O(1) performance.
Named Sets for Dynamic Blocklists
|
|
Interval Sets for CIDR Ranges
|
|
Maps for Policy Dispatch
Maps let you define service-specific policies:
|
|
Security Considerations
- Rule order matters. Specific rules (accepted services, established connections) must come before catch-all logging and drops. nftables evaluates top-to-bottom per chain.
- Default deny, allow by exception. Your base policy for
inputandforwardshould bedrop. Only open ports you explicitly need. - Separate logging chains keep the main path clean. For complex rulesets,
jumpto aloggingchain instead of sprinklinglogverdicts everywhere. - Audit your ruleset regularly. Save
nft list rulesetoutput and diff it over time:1nft list ruleset > /root/firewall-baseline-$(date +%F).txt - Backup
/etc/nftables.confalongside other server configs. Include it in whatever backup strategy you use for/etc/.
nftables replaces the legacy iptables/ip6tables/arptables/ebtables stack with one unified, faster, more expressive framework. The migration from Debian 11 to 12 or Ubuntu 22.04 to 24.04 is the perfect time to make the switch — the tools are already on your system, your kernel supports it, and the syntax is clean enough that a single config file can secure a server for years.