Description
The Linux kernel filters packets with netfilter. On Debian, its rules are managed by one of these tools, each started at boot by its own service:
ufw, a simple front end, active whenufw statusshowsStatus: active;nftables, rules loaded from/etc/nftables.confbynftables.service;firewalld;iptables-persistent, rules restored bynetfilter-persistent.service.
This control requires exactly one of them to be enabled. Its default policy is covered by CB-DEB-NET-006.
Debian 13 favours nftables and the CIS Benchmarks favour ufw; both are accepted. ufw on
Debian writes its rules through iptables-nft, so nft list ruleset also shows them: that
does not count as a second firewall as long as nftables.service is not enabled. Docker and
libvirt add their own rules beside the host firewall; they are not counted.
Rationale
A host firewall limits the services reachable from the network to the ones that are meant to be, even when a package starts a new listener or a service binds to all addresses by mistake. Two firewall tools flush or reorder each other's rules on reload, which leaves the host either wide open or cut off after a restart.
Impact
The automatic fix installs and enables ufw when no firewall is active. It allows the SSH ports
first, but ufw then drops every other incoming connection: services such as web or database
servers become unreachable until they are allowed with ufw allow <port>/<protocol>. It is
disabled by default; list the listening services with ss -tulpn and allow them before
enabling it.
Audit
ufw status(if installed):Status: activecounts as one.systemctl is-enabled nftables.service firewalld.service netfilter-persistent.service: eachenabledcounts as one. Exactly one must be counted.
Automated: every check must pass. Scanners report each check by its id.
single-firewall
Firewall tools enabled at boot.
script: |
active=
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
active="$active ufw"
fi
for u in nftables firewalld netfilter-persistent; do
[ "$(systemctl is-enabled "$u.service" 2>/dev/null)" = enabled ] && active="$active $u"
done
echo "active firewall tools:${active:- none}"
set -- $active
[ "$#" -eq 1 ]
Remediation
- No firewall: install ufw, allow SSH and the other services that must stay reachable, then
enable it:
Or writeapt install ufw ufw allow 22/tcp ufw enable/etc/nftables.conf(see CB-DEB-NET-006) and runsystemctl enable --now nftables. - Several tools: keep one, then disable the others, for example
ufw disable,systemctl disable --now nftablesorapt purge iptables-persistent.
| Restart | none |
| Disruption | high |
| Lockout risk | yes: can cut off administrative access. Off by default in generated scripts and playbooks, opt in through a tailoring file. |
Rollback
Run ufw disable. Installed packages can stay.
enable-ufw
# CB-DEB-NET-005: when no firewall is active, install and enable ufw, allowing SSH first.
# Does nothing when exactly one firewall tool is active; fails when several are (choice left to a human).
PATH="$PATH:/usr/sbin"
active=
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
active="$active ufw"
fi
for u in nftables firewalld netfilter-persistent; do
[ "$(systemctl is-enabled "$u.service" 2>/dev/null)" = enabled ] && active="$active $u"
done
# shellcheck disable=SC2086 # word splitting is the point
set -- $active
[ "$#" -eq 1 ] && exit 0
if [ "$#" -gt 1 ]; then
echo "several firewall tools are enabled:$active; keep one by hand" >&2
exit 1
fi
if ! command -v ufw >/dev/null 2>&1; then
DEBIAN_FRONTEND=noninteractive apt-get install -y -q ufw >/dev/null || exit 1
fi
for f in /etc/ufw/ufw.conf /etc/ufw/user.rules /etc/ufw/user6.rules; do cb_backup "$f"; done
# Allow every port sshd listens on before turning the firewall on.
for p in $(sshd -T 2>/dev/null | awk '$1 == "port" { print $2 }'); do
ufw allow "$p/tcp" comment 'SSH (CIRSEC Baselines)' >/dev/null || exit 1
done
ufw --force enable
Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.
References
- ufw(8), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- Debian Wiki - nftables Debian, vendor-doc, accessed 2026-10-18
- NIST SP 800-41 Rev. 1 - Guidelines on Firewalls and Firewall Policy NIST, guide, accessed 2026-10-18
Mappings
| Framework | ID | Relation | Note |
|---|---|---|---|
| NIST SP 800-53 Rev. 55.1.1 | SC-7 | partial |
How this control relates to the other framework's: equivalent, stricter, looser, partial or related.
Threats mitigated
Related controls
- related
CB-DEB-NET-006Drop incoming and forwarded traffic by default, with SSH allowed
People and lifecycle
| Authors |
|
| Verified | 2026-10-18 by Sn0wAlice on 12, Proxmox VM 921, Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64. No firewall reported, fix installs ufw, allows the SSH port, enables it; SSH by key still accepted locally and port 22 reachable from the lab host, other ports blocked; ufw plus nftables.service reported and fix refuses; kept after reboot. |
| Verified | 2026-10-18 by Sn0wAlice on 13, Proxmox VM 931, Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64. No firewall reported, fix installs ufw, allows the SSH port, enables it; SSH by key still accepted locally and port 22 reachable from the lab host, other ports blocked; ufw plus nftables.service reported and fix refuses; kept after reboot. |
| Introduced in | v0.1.0 |
| Created, updated | 2026-10-18, 2026-10-18 |
| Translations | fr |