Skip to content

NetworkingFirewalls

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
  1. Before you start
  2. Step 1: Write the ruleset
  3. Step 2: Check the syntax
  4. Step 3: Apply with a safety net
  5. Step 4: Restrict a port to your addresses
  6. Step 5: Open more ports
  7. Log what is dropped
  8. Troubleshooting
  9. Next steps

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 with sudo 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:

Bash
sudo nano /etc/nftables.conf

Replace its content with:

Text
#!/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

Bash
sudo nft -c -f /etc/nftables.conf

No 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:

Bash
sudo systemd-run --on-active=5min /usr/sbin/nft flush ruleset
sudo nft -f /etc/nftables.conf

Open 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:

Bash
sudo systemctl enable nftables

Verify:

Bash
sudo nft list ruleset

Step 4: Restrict a port to your addresses

To allow SSH only from the addresses in admin_hosts, change the SSH line to:

Text
tcp dport 22 ip saddr @admin_hosts accept

Add or remove addresses at runtime without reloading the whole file:

Bash
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:

Text
udp dport 51820 accept
tcp dport 25565 accept
Bash
sudo nft -c -f /etc/nftables.conf
sudo nft -f /etc/nftables.conf

Log what is dropped

To see dropped packets, add a rate-limited log rule as the last line of the input chain:

Text
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

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

إنشاء كلمة مرور

Please confirm