Linux Firewall Basics with nftables and ufw Explained Simply

A fresh VPS gets probed within seconds—here's how to lock it down with nftables and ufw, why they're not really rivals, and the Docker gotcha that silently bypasses your rules.

Linux Firewall Basics with nftables and ufw Explained Simply

Three seconds. That's roughly how long a fresh VPS with a public IP survives before something starts knocking. Not a targeted attack — just the background radiation of automated scanners cycling through every port they can find. If you've ever run journalctl -u ssh on a machine you spun up an hour ago, you know the feeling: dozens of failed login attempts from addresses you've never heard of.

That's why we're going to talk about the Linux firewall basics with nftables and ufw — and why the choice between them isn't the binary you might expect. Both are legitimate tools, both do the same fundamental job, and picking the wrong one for your context costs you either time or precision. Not dramatic, but annoying.

Key takeaways

  • UFW is a friendly frontend — on modern systems it drives nftables underneath, not iptables as it once did.
  • nftables replaced iptables in the kernel; you get one tool for IPv4, IPv6, ARP and bridges instead of four.
  • Always allow SSH before you enable the firewall. This is the classic lockout.
  • Docker inserts its own nftables chains that bypass UFW's input rules — this surprises a lot of people.
  • Firewalld is zone-based and lives on RHEL-family systems; UFW is the Debian/Ubuntu counterpart.

What a Linux firewall actually does (and why "firewall" is a fuzzy word)

Netfilter is the kernel subsystem. It's been there since the 2.4 kernel era, and everything else — iptables, nftables, ufw, firewalld — sits on top of it. When you type ufw allow 22, you're talking to netfilter through a couple of layers of abstraction.

Here's where people get confused. They think "firewall" means a separate program guarding the door. It's not. It's a set of rules evaluated in order, inside the packet path, before the packet reaches your application. Every packet that touches your NIC passes through these chains whether you configured anything or not — if there are no rules, the default is usually to accept everything.

Which brings up an obvious problem.

The default-deny reflex you should adopt immediately

A fresh install has no firewall rules worth mentioning. Every port a service is listening on is reachable from the internet. That's the situation on every VPS you've ever provisioned, and most people leave it that way for days.

The right starting posture is deny inbound by default, allow outbound, then punch holes only for services you intend to expose. This one habit eliminates an entire category of incident. I've seen a friend's personal project get indexed by a search engine and start serving spam because a dev database was listening on 0.0.0.0:5432 with a weak password. Ten seconds of firewall config would have prevented a three-day cleanup.

nftables vs iptables — the shift you should already have made

If you're on a system running a kernel from the last several years, nftables is the default packet-filtering backend. iptables still works, but on most distributions it's a compatibility shim called iptables-nft that translates your old syntax into nftables rules under the hood.

nftables vs iptables — the shift you should already have made

Why does this matter? Because mixing the two naively produces puzzles. Picture a machine where one script loads iptables-legacy rules and another loads nftables rules. Both appear active. Packets get processed by both stacks in a defined order, and your careful debugging session turns into an archaeology exercise.

Practical advice: pick one. If you're writing rules by hand, pick nftables.

Aspectiptablesnftables
Protocol families (v4/v6/bridge/arp)Separate tools per familyOne tool, one syntax
Rule updatesFull table replaceAtomic incremental updates
Sets for IP listsipset moduleNative sets and maps
SyntaxOne flag per conceptStructured, closer to a language
StatusLegacy / compat layerPrimary netfilter interface

The atomic-update point deserves a mention. With iptables, if you flush a chain and re-apply rules, there's a window where packets are unfiltered. With nftables, applying a ruleset is atomic — packets see either the old set or the new one, never a gap. On a server handling actual traffic, that matters.

How to use nftables in Linux

You don't need much to get started. The nft binary talks to the kernel, and the kernel does the rest.

How to use nftables in Linux

The anatomy of a ruleset: tables, chains, rules

Three concepts, stacked:

  • A table groups rules by address family (inet handles both IPv4 and IPv6, which is why you should use inet and not ip)
  • A chain is a list of rules, attached to a hook — input, forward, output — with a priority
  • A rule matches packets and applies a verdict: accept, drop, jump

Here's a minimal, sane baseline:

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        iif lo accept
        tcp dport 22 accept
        icmp type echo-request accept
    }
    chain forward {
        type filter hook forward priority 0; policy drop;
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
}

Save that to /etc/nftables.conf, then nft -f /etc/nftables.conf. The connection-tracking rule at the top is what makes this workable: it lets return traffic through without you writing an outbound rule for every protocol you might use. Forget it and nothing works — not DNS, not apt, not curl.

Verifying the ruleset (and not locking yourself out)

Run nft list ruleset. If your rules show up with the right policies, you're good. But before you walk away from an SSH session, test from a second terminal. That's not paranoia; that's the standard practice on every remote machine I've touched in the last year, and it has saved me twice.

Want proof that only the ports you intended are reachable? Run ss -tlnp locally to see what's listening, and scan the box from another machine with nmap. Two commands, and the picture is unambiguous. I'd trust that over any firewall status output.

UFW — the friendly wrapper that now rides on nftables

UFW stands for Uncomplicated Firewall. The name is honest about the goal: reduce the mental overhead of netfilter for people who just want a working baseline.

UFW — the friendly wrapper that now rides on nftables

On a Debian or Ubuntu box:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw enable

Four lines. The order matters — allow 22 before enable — because once you enable, the deny policy takes effect immediately.

Does UFW work with nftables?

Yes. On current distributions, UFW translates its rules into nftables rules rather than iptables rules. You can verify this by running nft list ruleset after enabling UFW and looking for the ufw-* chains — they live in the nftables space. The user-facing commands are unchanged, but the mechanism underneath is nft. In practice this means you get one backend to reason about while retaining the simpler interface.

One caveat worth flagging: older documentation and tutorials describe UFW as "a frontend for iptables." That description is stale. If you follow a guide written before the shift and try to debug UFW behavior by inspecting iptables output, you'll be looking in the wrong place.

UFW and the Docker surprise

Here's the one that bites people. You configure UFW to deny inbound by default. You publish a container with -p 8080:80. You expect the port to be blocked. It isn't.

Docker manipulates netfilter directly and inserts its own chains in the forward path, with rules that precede or bypass UFW's input filtering. The container's port gets exposed regardless of what ufw status says. This is not a bug in UFW — it's a consequence of two tools targeting the same layer with different assumptions. The fix is either to bind container ports to 127.0.0.1 when they don't need external exposure, or to filter in the DOCKER-USER chain yourself.

If you run containers, budget time to read that chapter. It's the single most common misconfiguration I see on hobby and small-business servers.

Which firewall should you pick?

There is no universal winner, because "best" depends on what you're administering. For a single server with a handful of services, UFW is the pragmatic choice: fast to configure, hard to get subtly wrong, and easy for the next person to read. For a fleet with heterogeneous rules, or for anything where you need sets, rate limiting per source, or atomic reloads, nftables is the better investment. Firewalld only makes strong sense if you're already in the RHEL ecosystem where it's the default and its zones map onto how your systems are organized.

My honest opinion: start on UFW, migrate to raw nftables the moment a requirement forces you to. Not before.

What is better, UFW or FirewallD?

Neither is objectively "better" — they solve the same problem with different mental models. UFW is port-and-rule oriented: you allow services or ports, and that's mostly it. Firewalld is zone-oriented: you assign interfaces to zones, and each zone carries its own ruleset. The zone model is genuinely useful when a machine has multiple network interfaces with different trust levels — a public interface and an internal one, for instance.

On a system with one NIC, UFW's simpler model is usually enough and takes less time to learn. On a system with several interfaces and a firewall policy tied to that topology, Firewalld's zones save you from reinventing the same structure with named rule sets.

The rule that outlives the tool

Tools change. The iptables-to-nftables transition is one chapter; there will be another. What doesn't change is the posture: assume the internet is hostile, expose only what you must, and verify from outside. That single mindset makes you choose the right commands regardless of which frontend is currently in vogue.

And if you take one thing from this article, take the verification habit. Firewall config isn't done when the command returns. It's done when you've scanned the box from another machine and seen nothing you didn't put there. Everything else is bookkeeping.

Daniel Lockhart

Daniel Lockhart

Daniel Lockhart is an author and policy analyst whose work focuses on open source licensing, digital sovereignty policy, data privacy regulation, and public sector procurement. He helps organizations navigate the legal and strategic complexities of technology governance, with a particular emphasis on how public institutions can maintain control over their digital infrastructure. His writing translates dense regulatory and technical topics into practical guidance for decision-makers.

See all articles →

Related articles