HardStacked
Share:

Firewall · nftables vs UFW

“UFW is not installed” does not mean you have no firewall (the Debian 13 trap)

2026-08-03 · HardStacked · 6 min read

You ran a security audit on a Debian 13 server and the report came back with “no firewall detected”, or the slightly more honest variant, “UFW is not installed”. Now you're wondering if your box has been sitting on the internet naked.

Short answer: probably not, and the alert is telling you more about the audit tool than about your server.

1. What Debian actually ships

Debian 13 (trixie) does not install UFW by default. It never has. UFW is an Ubuntu convenience wrapper, and a lot of tutorials written for Ubuntu get copy-pasted into “Debian hardening guides” without anyone checking what Debian actually puts on disk.

What Debian ships is nftables. On a fresh trixie install, the nftables package is present and the nft binary is there. We verified this ourselves on a clean Debian 13 VM, fully updated, zero modifications, while building our own audit tool: nftables present out of the box, UFW absent. That is the normal, expected state of a Debian server.

So when an audit greps for the ufw binary, finds nothing, and prints “no firewall”, it has tested for the presence of one specific Ubuntu-flavored package and concluded something about your entire packet filtering capability. Those are not the same question.

2. Check for yourself, in two minutes

Don't take the audit's word for it, and don't take mine either. Ask the kernel.

$ sudo nft list ruleset

This prints every nftables rule currently loaded. Not what's configured in some file, what is actually active right now. Three outcomes:

Three outcomes, three different answers

  1. Command not found. Before you conclude anything, check your PATH. nft lives in /usr/sbin, and depending on how you got your shell (su without a login shell, some sudo configurations, a cron context), /usr/sbin may not be in it. Try /usr/sbin/nft list ruleset. We got bitten by exactly this while building our audit tool: the scan reported nft as absent when it was sitting right there, outside the script's PATH. We now force a sane PATH at the top of the scanner because of it. If the binary genuinely isn't on disk, that's unusual for Debian 13 and worth investigating how the box was built.
  2. Empty output. The tool is installed but zero rules are loaded. This is the default state of a fresh Debian install, and yes, it means no host firewall is currently filtering. That is a real finding. But notice how different it is from “UFW is not installed”: the correct response is “load rules into the framework Debian gave you”, not “install a second framework”.
  3. Rules print out. You have a firewall. It might have been put there by an nftables config, by firewalld, by fail2ban, by Docker, by your own past self. The audit that said “no firewall” was flat wrong, and if you had followed its advice, you'd have been about to stack a second ruleset on top of a working one. More on why that's bad below.

Then check persistence:

$ systemctl status nftables $ systemctl is-enabled nftables $ cat /etc/nftables.conf

Rules loaded by hand with nft commands die at reboot. Rules in /etc/nftables.conf with the nftables service enabled come back. A firewall that evaporates on reboot is a firewall you don't have.

3. Why audit tools get this wrong

The root cause is worth understanding because it generalizes way past firewalls: most audit checks test for the presence of a package instead of the presence of a capability.

Testing “is the ufw binary on disk” is cheap and portable. Testing “is traffic actually being filtered, by anything, right now” requires understanding that Linux has one packet filter (the kernel's netfilter/nftables) and half a dozen frontends that drive it: ufw, firewalld, iptables-nft, raw nft, plus daemons like Docker and fail2ban that inject their own rules. A check written on Ubuntu, where ufw is the blessed frontend, ships unchanged and produces a permanent false alarm on every Debian box it touches.

You'll find the same pattern all over security tooling. “Antivirus not installed” on a box where the real question is whether anything scans anything. “Fail2ban absent” on a host where sshd only accepts keys and the port is filtered upstream anyway. Package presence is a proxy metric, and proxy metrics lie the moment you change distributions.

When we hit this in our own tool, the fix wasn't to suppress the message. It was to make the rule distro-aware: on Debian, ufw being absent is stated as a neutral fact, and the actual judgment runs on nftables, with three distinct verdicts. nftables absent is not the same finding as nftables present with zero rules, and neither is the same as rules loaded. And when the tool can't check something at all, it says “not verified” instead of “nothing found”. Those two get conflated constantly, and the difference matters: “nothing detected” is a claim about your server, “not verified” is an admission about the tool.

4. The trap: installing UFW to silence the alert

Here's where a false positive becomes a real incident. The audit says “install ufw”, you apt install ufw && ufw enable, alert gone, ticket closed.

On a box that already had nftables rules, you now have two rulesets in the same kernel. nftables evaluates every base chain hooked at a given point, in priority order. An accept verdict in one chain does not exempt the packet from the other chains, and a drop anywhere is final. So your existing rules and ufw's rules are now both live, and the effective policy is the intersection of two configurations that were each written assuming they were alone.

Two failure modes, pick your poison. Either ufw's default deny-incoming eats traffic your existing rules were deliberately allowing, and if that traffic is SSH on a nonstandard port, you've just locked yourself out of a remote server to fix a finding that was never real. Or your existing permissive rules and ufw's rules interleave into something neither config describes, and you walk away believing “ufw enable” means what the ufw docs say it means, while the actual policy is something nobody has ever read. A firewall you misunderstand is arguably worse than the empty ruleset you started with, because now you've stopped looking.

If the ruleset genuinely was empty and you prefer ufw's ergonomics, fine, install it on the empty box, it drives nftables underneath anyway. Just make that decision from nft list ruleset output, not from an audit line written for a different distribution.

5. What to actually verify

Forget “is package X installed”. Three questions, in order:

Are rules loaded? sudo nft list ruleset. Read it. If it's beyond you today, at minimum identify the input chain's policy: policy drop with explicit accepts is a firewall, policy accept with nothing else is decoration.

Do they survive a reboot? Service enabled, rules in the config file, and ideally you've rebooted once and looked again. Assumption is not verification.

What do they let through, and to what? ss -tlnp shows what's actually listening. Cross it against your rules. A perfect firewall in front of a service that shouldn't be running is solving the wrong problem. And if you're on a cloud VPS, remember the provider's security groups filter before your packets ever reach nftables; your host firewall is the second layer, not the only one.

The deeper lesson

The tool we hit this in is audit-pi, and it's free: github.com/Hardstacked/audit-pi, MIT. On Debian it states ufw's absence as a neutral fact and runs the actual judgment on nftables, with the three verdicts above kept separate. It also reports “nothing detected” and “not verified” as two different things, because they are: one is a claim about your server, the other an admission about the tool. Read the rule, disagree with it, send a patch.

And if you've run the commands above and still aren't sure what your server is letting through, write to me. Worst case you'll get a straight answer.

Share: