nftables is the packet-filtering framework in current Linux kernels, and Debian has built on it for several releases: since Debian 10 the iptables command itself has been the iptables-nft variant, which writes nftables rules underneath. This guide sets up a small host firewall on Debian 12 (bookworm) or Debian 13 (trixie) that allows SSH and web traffic, drops everything else, survives a reboot, and explains how it sits next to ufw, Docker and a cloud firewall in front of the server.
How the pieces fit together
- nft is the command-line tool. Rules live in tables that contain chains; a chain attached to a hook such as
inputsees packets addressed to the server. A table of theinetfamily covers IPv4 and IPv6 at once. - /etc/nftables.conf is the file Debian loads at boot when
nftables.serviceis enabled, as described on the Debian wiki. Debian 12 ships nftables 1.0.6 and Debian 13 ships 1.1.3 (package tracker). - iptables-nft, ufw and Docker: ufw manages netfilter through iptables, and Docker uses iptables by default. On Debian that means their rules also end up in nftables and show up in
nft list ruleset. Two managers editing the same firewall is the most common source of confusion, so pick one per server.
Before you change anything: avoid locking yourself out
A firewall mistake on a remote server can cut your own SSH session. Three habits prevent most accidents: keep your current SSH session open while testing from a second one, check the file before loading it, and schedule an automatic undo that you cancel once you have confirmed access. systemd-run can create that timer (see systemd-run(1)):
sudo apt update
sudo apt install nftables
# undo in 5 minutes unless cancelled: removes all rules
sudo systemd-run --unit=nft-rollback --on-active=300 /usr/sbin/nft flush rulesetA minimal ruleset for SSH and HTTP(S)
The following file is adapted from the server example on the nftables wiki. It drops inbound traffic by default, accepts replies to connections the server started, allows loopback, rate-limits ping, keeps the ICMPv6 neighbour discovery messages that IPv6 needs, and opens TCP 22, 80 and 443.
#!/usr/sbin/nft -f
# /etc/nftables.conf
flush ruleset
table inet firewall {
chain inbound_ipv4 {
icmp type echo-request limit rate 5/second accept
}
chain inbound_ipv6 {
icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
icmpv6 type echo-request limit rate 5/second accept
}
chain inbound {
type filter hook input priority 0; policy drop;
ct state vmap { established : accept, related : accept, invalid : drop }
iifname lo accept
meta protocol vmap { ip : jump inbound_ipv4, ip6 : jump inbound_ipv6 }
tcp dport 22 accept
tcp dport { 80, 443 } accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
}No output chain is defined, so outbound traffic stays allowed. If SSH listens on another port, change 22 before loading the file.
Check, load and keep it across reboots
# syntax check only, nothing is applied
sudo nft -c -f /etc/nftables.conf
# apply
sudo nft -f /etc/nftables.conf
sudo nft list ruleset
# now open a NEW SSH session; if it works, cancel the undo timer
sudo systemctl stop nft-rollback.timer
# load the file at every boot
sudo systemctl enable nftables.serviceThe -c (--check) option validates commands without applying them, as documented in nft(8). Rules added at runtime with nft add rule are lost at reboot unless you also put them in the file.
Limiting SSH to known addresses
If you always connect from fixed addresses, a named set keeps the rule readable. Add the set inside the table and replace the plain SSH rule:
set ssh_admins {
type ipv4_addr
flags interval
elements = { 203.0.113.10, 198.51.100.0/24 }
}
# in chain inbound, instead of: tcp dport 22 accept
ip saddr @ssh_admins tcp dport 22 acceptThe interval flag lets the set hold address ranges. This rule only matches IPv4; if you manage the server over IPv6, add an ipv6_addr set and an ip6 saddr rule as well, or you will lock yourself out over IPv6.
Where ufw, Docker and a cloud firewall fit
- ufw: fine for simple servers, but do not also load your own
/etc/nftables.confon the same machine. Choose one. - flush ruleset removes every table, not just yours. The nft manual is explicit that it leaves an empty ruleset. Reloading the file therefore also wipes rules that Docker, ufw or other tools created.
- Docker hosts: Docker's own installation guide states that rules created with
nftare not supported on a system with Docker installed, and that published container ports bypass ufw and firewalld rules. On a Docker host, follow Docker's packet filtering guidance (iptables and theDOCKER-USERchain) instead of a hand-writtenflush rulesetfile. - Cloud firewall: a firewall in front of the server stops unwanted traffic before it reaches the operating system and still works if a host rule is wrong. On Skyline Cloud's Riyadh VPS, the cloud firewall is managed from the console and applies outside the server (VPS overview). Use it as the outer layer and nftables as the inner one, with the same list of open ports in both.
Common pitfalls
- Forgetting the ICMPv6 neighbour discovery rules: IPv6 connectivity breaks in ways that look like a network fault.
- Setting
policy droponforwardon a server that routes traffic, such as a VPN gateway or a container host. Adjust that chain to the role of the server. - Testing only from the session you already have open. Established connections keep working even when new ones are blocked.
- Editing the file but not reloading it, or reloading it but not enabling the service.
What you should do
- Decide which tool owns the firewall on each server: a hand-written nftables file, ufw, or Docker's rules plus
DOCKER-USER. - Start from the minimal ruleset above, open only the ports the server really serves, and keep the file under version control.
- Always run
nft -c -ffirst and keep a timed rollback until a fresh SSH login succeeds. - Enable
nftables.serviceso the rules come back after a reboot, and check withnft list ruleset. - Mirror the same open ports in the cloud firewall in front of the server, so a host mistake is not the only line of defence.
Sources
- wiki.debian.org/nftables
- wiki.nftables.org/wiki-nftables/index.php/Simple_ruleset_for_a_server
- manpages.debian.org/trixie/nftables/nft.8.en.html
- manpages.debian.org/trixie/systemd/systemd-run.1.en.html
- manpages.debian.org/trixie/ufw/ufw.8.en.html
- tracker.debian.org/pkg/nftables
- docs.docker.com/engine/network/packet-filtering-firewalls
- docs.docker.com/engine/install/debian
- cloud.alskyline.com/vps-saudi-arabia
Comments
0 total · 0 threads