Firewalls & Network Security

nftables Firewall Basics on Debian 12 and 13: A Minimal Ruleset for SSH and Web Servers

Build a small, readable nftables firewall on Debian 12 or 13 that allows SSH and HTTP(S), survives reboots and fits next to ufw, Docker and a cloud firewall.

Published
Reading time
6 min read

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 input sees packets addressed to the server. A table of the inet family covers IPv4 and IPv6 at once.
  • /etc/nftables.conf is the file Debian loads at boot when nftables.service is 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 ruleset

A 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.service

The -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 accept

The 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.conf on 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 nft are 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 the DOCKER-USER chain) instead of a hand-written flush ruleset file.
  • 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 drop on forward on 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 -f first and keep a timed rollback until a fresh SSH login succeeds.
  • Enable nftables.service so the rules come back after a reboot, and check with nft 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

SKYLINE Engineering

@skyline

The engineering team at SKYLINE Industrial Solutions. We publish field-tested guides drawn from real KSA and GCC deployments.

See author profile
SKYLINE engineering services

Need this implemented for you?

Reading is free — building it right takes a team. SKYLINE engineers ship Firewalls & Network Security for Aramco vendors, banks, hospitals and government agencies across Saudi Arabia. Talk to us before you start.

Aramco Approved Contractor ISO 9001 · ISO 27001 SAMA CSF aligned NCA ECC ready 247+ KSA clients

Comments

0 total · 0 threads
Be the first to leave a comment.