Network Hardening

Firewalls

Host firewalls with nftables and ufw: default deny, allow what you need.

Trimming unneeded services (see Reducing Exposed Services) shrinks your attack surface, but a firewall gives you a second, independent gate: even if a service starts listening unexpectedly, the firewall can still refuse connections to it. The goal is a default-deny policy for inbound traffic. Instead of enumerating everything to block, you drop everything and then allow the short list of ports you actually serve. That design fails safe: a mistake tends to block too much (which you notice immediately) rather than expose too much (which you might never notice). On Ubuntu the default frontend is ufw, and underneath it the kernel framework is nftables.

nftables concepts

nftables is the modern kernel packet-filtering framework, the successor to iptables. Three concepts matter:

ConceptWhat it is
TableA container for chains, scoped to a protocol family (inet covers both IPv4 and IPv6)
ChainAn ordered list of rules attached to a hook such as input, forward, or output, with a default policy
RuleA match plus a verdict (accept, drop, reject)

An inbound packet traverses the input chain top to bottom; the first matching rule’s verdict wins, and if nothing matches, the chain’s default policy applies. Default-deny means setting that policy to drop.

A minimal default-deny ruleset

Written directly in nftables, a host firewall that allows only SSH and established replies looks like this. Save it as /etc/nftables.conf.

sudo tee /etc/nftables.conf >/dev/null <<'EOF'
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        iif "lo" accept
        ct state established,related accept
        ct state invalid drop
        tcp dport 22 ct state new accept
        ip protocol icmp accept
    }
    chain forward { type filter hook forward priority 0; policy drop; }
    chain output { type filter hook output priority 0; policy accept; }
}
EOF
sudo systemctl enable --now nftables
sudo nft list ruleset | head -8

Expected output:

table inet filter {
	chain input {
		type filter hook input priority 0; policy drop;
		iif "lo" accept
		ct state established,related accept
		ct state invalid drop
		tcp dport 22 ct state new accept

The order is deliberate: accept loopback, accept replies to connections you initiated, drop garbage, then allow new SSH connections. Everything else falls through to policy drop.

ufw as a friendly frontend

Most of the time you do not hand-write nftables. ufw (Uncomplicated Firewall) is Ubuntu’s default frontend: you express intent in plain commands and it generates the underlying rules. This is the recommended path on Ubuntu.

# Default deny inbound, allow outbound, then permit SSH with rate limiting
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw limit 22/tcp
sudo ufw enable
sudo ufw status verbose

Expected output:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)

To                         Action      From
--                         ------      ----
22/tcp                     LIMIT       Anywhere
22/tcp (v6)                LIMIT       Anywhere (v6)

ufw limit is a useful touch: it allows SSH but throttles a source address that opens too many connections in a short window, which blunts brute-force scans. Add other services as needed, for example sudo ufw allow 443/tcp for HTTPS.

Choosing between the two

ufwnftables directly
Ease of useHigh, plain intent commandsLower, full syntax
Best forSingle hosts, common cases, Ubuntu defaultsComplex policy, NAT, fine-grained matching
Reversibleufw disable / ufw resetEdit and reload /etc/nftables.conf
RelationshipGenerates rules in the kernel frameworkIs the kernel framework

On Ubuntu, firewalld is available but not the default; ufw is the expected tool. Drive ufw day to day and drop to nftables only when you need something ufw cannot express. Do not run two frontends managing the same rules at once.

Practical Guidance

  1. Set the inbound policy to deny by default and add explicit allow rules only for ports you actually serve; a firewall that starts from deny fails safe.
  2. On Ubuntu, prefer ufw for everyday work and reserve raw nftables for policy it cannot express, but never run two managers over the same ruleset.
  3. Always allow loopback and established/related traffic before the deny takes effect, or you will break local sockets and reply packets.
  4. Use ufw limit 22/tcp (or an nftables rate limit) to throttle SSH brute-force attempts at the firewall.
  5. Enable the firewall while keeping a console or second session open, and test on a snapshot VM, since a wrong rule can lock you out of SSH.
  6. Treat the firewall as a second gate behind service reduction, not a substitute for it: fewer listeners means simpler, safer rules.