Firewall Baseline: Default-Deny Plus the Minimal Whitelist
Port audit first, then two copy-paste baselines (ufw five-minute version, nftables standard version), three maintenance routines and the three classic failure
# The only correct mental model: default-deny plus a minimal whitelist
Firewalls have two mental models — "list what to block" and "list what to allow". The first list never ends; the second only needs you to think hard about which doors the business actually uses. **Default-deny inbound + minimal whitelist** is the industry consensus baseline. This piece gives two copy-paste implementations (ufw simple, nftables standard) plus the maintenance discipline. Pair it with the SSH hardening checklist (this is action 6, in full).
## Step 1 — port audit: the evidence for your whitelist
Before configuring anything, answer "what does this machine offer the world":
```bash
# every listening port with its process — the audit's starting point
ss -tlnp
# ask three questions per line: what service? must it be public? who uses it?
# 127.0.0.1:xxxx = local service (the correct posture for DB/cache) — no whitelist needed
# 0.0.0.0:xxxx = open to the internet — each needs an existence reason
# typical minimal set: 22 (SSH) + 80/443 (web); DB/Redis/panel ports must NOT appear on 0.0.0.0
```
Common findings: databases listening on 0.0.0.0 by default (naked from install day), admin panels open to the world, forgotten dev-time services. **The firewall is the last net; the service's own bind address is the first** — move what can bind to 127.0.0.1 first; only the rest enters the whitelist discussion.
## ufw baseline: the five-minute version
```bash
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'ssh'
ufw allow 80/tcp comment 'http'
ufw allow 443/tcp comment 'https'
# panel-class ports: office network only (an order of magnitude safer than world-open)
ufw allow from to any port 8888 proto tcp comment 'panel-office-only'
# enable (⚠ confirm 22 is allowed and your SSH session is alive first; port changed? allow the new port first)
ufw --force enable
ufw status numbered # verify: numbered rule list
nc -zv -w3 3306 || echo "3306 closed (expected)"
```
## nftables baseline: the standard version (distros/hosts without ufw)
```nftables
# /etc/nftables.conf — default-deny + state tracking + minimal whitelist
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
ct state invalid drop
iif "lo" accept
tcp dport { 22, 80, 443 } accept
icmp type echo-request limit rate 5/second accept
log prefix "nft-drop: " limit rate 3/minute
}
chain forward { type filter hook forward priority 0; policy drop; }
chain output { type filter hook output priority 0; policy accept; }
}
```
Load with `nft -f /etc/nftables.conf` and persist per distro. The log line samples refusals without flooding; the icmp rate limit keeps diagnostics possible without handing scanners a echo amplifier.
## Maintenance discipline: three routines
1. **Whitelist changes are tickets.** Every added port gets a reason, an owner and an expiry review; ports outlive the projects that opened them.
2. **Quarterly re-audit.** Re-run `ss -tlnp` and diff against the whitelist; anything new must justify itself or die.
3. **Egress is a later chapter.** Default-allow outbound is fine at this stage; tighten egress only when a workload demands exfiltration control — doing it early breaks updates and monitoring in ways that train people to disable firewalls.
## Three classic failures
- **Enabling before verifying SSH access:** lock yourself out in one command. Confirm your live session survives (keep a console/IPMI channel known).
- **Whitelisting the panel to the world "for convenience":** panels are the most-attacked surface on a box; office-IP binding or a tailnet is the price of admission.
- **Trusting the cloud security group alone:** security groups and host firewalls are two layers with two owners; when they disagree, the more open one wins. Run both, default-deny both.