Ubuntu · Network

Run exactly one host firewall

When it fails: No host firewall is active, or several firewall tools compete for the same rules. Without a host firewall every listening service is reachable from the network; with several, rules overwrite each other in ways nobody planned.

CB-UBU-NET-005highactiveautomatedautomated fix, lockout riskprofile core

Description

The Linux kernel filters packets with netfilter. On Ubuntu, its rules are managed by one of these tools, each started at boot by its own service:

  • ufw, a simple front end, active when ufw status shows Status: active;
  • nftables, rules loaded from /etc/nftables.conf by nftables.service;
  • firewalld;
  • iptables-persistent, rules restored by netfilter-persistent.service.

This control requires exactly one of them to be enabled. Its default policy is covered by CB-UBU-NET-006.

ufw is installed by default on every Ubuntu release but left inactive (ENABLED=no in /etc/ufw/ufw.conf); ufw.service is enabled anyway, which is why the check reads ufw status rather than the unit state. Ubuntu documents ufw and the CIS Benchmarks favour it; nftables, firewalld and iptables-persistent are accepted too. On 22.04 and later, iptables is the iptables-nft variant, so ufw rules also show in nft list ruleset: that does not count as a second firewall as long as nftables.service is not enabled. 20.04 uses the legacy iptables back end. Docker and libvirt add their own rules beside the host firewall; they are not counted. The fix reads the SSH ports from sshd -T; on 24.04 and later, where sshd is started by ssh.socket, it first creates /run/sshd if no connection has started the service yet, and stops without enabling ufw if it cannot find any port.

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

  1. ufw status (if installed): Status: active counts as one.
  2. systemctl is-enabled nftables.service firewalld.service netfilter-persistent.service: each enabled counts 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: ufw is installed by default (otherwise apt install ufw). Allow SSH and the other services that must stay reachable, then enable it:
    ufw allow 22/tcp
    ufw enable
    
    Or write /etc/nftables.conf (see CB-UBU-NET-006) and run systemctl enable --now nftables.
  • Several tools: keep one, then disable the others, for example ufw disable, systemctl disable --now nftables or apt purge iptables-persistent.
Restartnone
Disruptionhigh
Lockout riskyes: 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-UBU-NET-005: when no firewall is active, enable ufw (installed by default on Ubuntu), 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
# sshd -T needs /run/sshd, which only exists once ssh.service has run (ssh.socket on 24.04 and later).
if command -v sshd >/dev/null 2>&1; then
  [ -d /run/sshd ] || mkdir -m 0755 /run/sshd
  ports=$(sshd -T 2>/dev/null | awk '$1 == "port" { print $2 }')
  if [ -z "$ports" ]; then
    echo 'cannot read the SSH ports with sshd -T; ufw left disabled' >&2
    exit 1
  fi
fi
if ! command -v ufw >/dev/null 2>&1; then
  DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=300 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 $ports; 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

Mappings

FrameworkIDRelationNote
NIST SP 800-53 Rev. 55.1.1SC-7partial

How this control relates to the other framework's: equivalent, stricter, looser, partial or related.

Threats mitigated

  • related CB-UBU-NET-006 Drop incoming and forwarded traffic by default, with SSH allowed

People and lifecycle

Authors
Verified2026-10-19 by Sn0wAlice on 20.04, Proxmox VM, Ubuntu 20.04 cloud image, kernel 5.4.0-216-generic, BIOS boot. ufw installed but inactive on a fresh install, reported; fix allows port 22 and enables ufw; SSH by key of a non-root user accepted on localhost and port 22 reachable from the lab host after the fix and after reboot, other ports dropped. nftables alone passes; iptables-persistent next to ufw reported as a second firewall.
Verified2026-10-19 by Sn0wAlice on 22.04, Proxmox VM, Ubuntu 22.04 cloud image, kernel 5.15.0-198-generic, BIOS boot. ufw installed but inactive on a fresh install, reported; fix allows port 22 and enables ufw; SSH by key of a non-root user accepted on localhost and port 22 reachable from the lab host after the fix and after reboot, other ports dropped; second remediate leaves it alone.
Verified2026-10-19 by Sn0wAlice on 24.04, Proxmox VM, Ubuntu 24.04 cloud image, kernel 6.8.0-147-generic, BIOS boot. ufw inactive on a fresh install, reported; fix run before any SSH connection (no /run/sshd) still reads port 22 and enables ufw; SSH by key of a non-root user accepted on localhost and port 22 reachable from the lab host after the fix and after reboot, other ports dropped.
Verified2026-10-19 by Sn0wAlice on 26.04, Proxmox VM, Ubuntu 26.04 cloud image, kernel 7.0.0-38-generic, BIOS boot. ufw inactive on a fresh install, reported; fix allows port 22 and enables ufw; SSH by key of a non-root user accepted on localhost and port 22 reachable from the lab host after the fix and after reboot. ufw plus nftables.service reported and the fix refuses; after iptables-persistent removed ufw, the fix reinstalled it with apt.
Introduced inv0.1.0
Created, updated2026-10-19, 2026-10-19
Translationsfr