Build a server firewall with nftables
Write a deny-by-default nftables ruleset for a Debian or Ubuntu server: established traffic, ICMP, SSH, web ports and allow-lists, applied with a rollback.
- Intermediate
- 20 min read
- Updated
Tested on: Debian 12, Debian 13, Ubuntu 24.04 LTS, Ubuntu 26.04 LTS
This guide is not available in your language yet, so it is shown in English.
On this page
nftables is the packet filtering framework of current Linux kernels and the default firewall on Debian. Its configuration is one readable file that covers IPv4 and IPv6. This guide writes a deny-by-default ruleset for a server, applies it safely and shows how to extend it. Replace 198.51.100.7 with your own IP address.
Before you start
- Log in with a sudo user and keep the web console ready if your service page shows one.
- Use one firewall tool. If UFW is active (
sudo ufw status), disable it first withsudo ufw disable, or stay with UFW: UFW firewall. - Install the tools if needed (Debian includes them; on Ubuntu run
sudo apt install nftables).
Step 1: Write the ruleset
Open /etc/nftables.conf:
sudo nano /etc/nftables.confReplace its content with:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set admin_hosts {
type ipv4_addr
flags interval
elements = { 198.51.100.7 }
}
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif "lo" accept
meta l4proto icmp accept
meta l4proto ipv6-icmp accept
tcp dport 22 accept
tcp dport { 80, 443 } accept
counter comment "dropped by policy"
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}What it does: replies to your own connections are allowed, loopback and ICMP are allowed, SSH and web ports are open, everything else that arrives is dropped. The admin_hosts set is ready for allow-lists (Step 4).
Step 2: Check the syntax
sudo nft -c -f /etc/nftables.confNo output means the file is valid.
Step 3: Apply with a safety net
Schedule an automatic rollback before you load the rules. If anything cuts your connection, the rules are flushed after five minutes:
sudo systemd-run --on-active=5min /usr/sbin/nft flush ruleset
sudo nft -f /etc/nftables.confOpen a new SSH session. If it works, cancel the rollback: systemctl list-timers shows the timer (its name starts with run-), and sudo systemctl stop with that name cancels it.
Make the ruleset load at boot:
sudo systemctl enable nftablesVerify:
sudo nft list rulesetStep 4: Restrict a port to your addresses
To allow SSH only from the addresses in admin_hosts, change the SSH line to:
tcp dport 22 ip saddr @admin_hosts acceptAdd or remove addresses at runtime without reloading the whole file:
sudo nft add element inet filter admin_hosts { 203.0.113.0/24 }
sudo nft delete element inet filter admin_hosts { 203.0.113.0/24 }Runtime changes are lost at reboot; add them to /etc/nftables.conf too. For IPv6 admin addresses, create a second set of type ipv6_addr and a matching rule with ip6 saddr.
Step 5: Open more ports
Add a line per service in the input chain, check and reload:
udp dport 51820 accept
tcp dport 25565 acceptsudo nft -c -f /etc/nftables.conf
sudo nft -f /etc/nftables.confLog what is dropped
To see dropped packets, add a rate-limited log rule as the last line of the input chain:
limit rate 5/minute log prefix "nft-drop: "Read the log with sudo journalctl -k | grep nft-drop.
Troubleshooting
SSH froze after loading the rules. Wait for the rollback timer, or use the console: sudo nft flush ruleset. Then fix the file. See locked out after a firewall change.
IPv6 stopped working. ICMPv6 is blocked. Keep the ipv6-icmp line.
Rules disappear after a reboot. The nftables service is not enabled, or another tool (UFW, Docker, firewalld) loads its own rules. Check systemctl status nftables.
Outgoing mail on port 25 fails although the output chain accepts it. Outbound port 25 is closed by default on HyperDC VPS. For services bought for a term of 3 months or longer, it is opened on request when you open a support ticket. Until then, send mail through a relay on port 587.
Docker containers lose network access. Docker manages its own rules; flush ruleset removes them. Restart Docker after reloading, or keep Docker's tables out of your flush as its documentation describes.
Next steps
- Forward ports and use NAT: ports and port forwarding.
- IPv6 on your server: IPv6 setup.
Frequently asked questions
Should I use nftables or UFW?
UFW is easier for common cases. nftables gives you full control in one readable file, sets for allow-lists and NAT. Choose one; do not maintain rules in both.
Do I need separate rules for IPv6?
No, if you use a table of family inet: its rules apply to IPv4 and IPv6. Remember to allow ICMPv6, which IPv6 needs to work.
Are my rules kept after a reboot?
Yes, when they are in /etc/nftables.conf and the nftables service is enabled. Rules added only with the nft command are lost at reboot.
Why allow ICMP at all?
ICMP carries error messages that keep connections working, such as path MTU discovery, and IPv6 needs ICMPv6 for neighbour discovery. Blocking it causes hard-to-find problems.
How do I see which rule blocks traffic?
Add counters or a log statement to the rule, for example log prefix followed by a text, then read the kernel log with journalctl -k.
Sources
- wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_…
- wiki.nftables.org/wiki-nftables/index.php/Simple_ruleset_for_a_server
- wiki.nftables.org/wiki-nftables/index.php/Sets
- manpages.debian.org/bookworm/nftables/nft.8.en.html
- wiki.debian.org/nftables
- freedesktop.org/software/systemd/man/latest/systemd-run.html