CoursesLinux hardeningNetwork hardening

A default-deny firewall (nftables)

A host firewall you can actually read.

Intermediate14 min · lesson 6 of 16

A hardened host runs a firewall that denies everything by default and allows only the specific traffic it needs. On modern Linux that firewall is nftables (the successor to iptables), and its great virtue is a readable, single-file ruleset you can review at a glance. The model is simple: set the input policy to drop, then explicitly accept established connections, loopback, and the handful of ports your services actually use — everything else is silently dropped.

INPUT CHAIN: DEFAULT-DENY EVALUATION
1Packet enters input chain
policy drop set (default-deny)
2Established / related?
accept replies to our own connections
3Loopback (lo)?
accept; drop ct state invalid
4Allowed port?
tcp 22, 80, 443, icmp -> accept
5Everything else
silently dropped by policy
Rules are matched in order; anything not explicitly accepted hits the drop policy and is silently discarded.
/etc/nftables.conf
table inet filter {
chain input {
type filter hook input priority 0; policy drop; # default-deny
ct state established,related accept # replies to our own connections
iif "lo" accept # loopback
ct state invalid drop
tcp dport 22 accept # SSH (ideally: from the bastion only)
tcp dport { 80, 443 } accept # the web service
ip protocol icmp accept # basic ping
}
chain forward { type filter hook forward priority 0; policy drop; }
chain output { type filter hook output priority 0; policy accept; }
}

Why default-deny changes everything

The power is in the policy drop. With a default-deny input chain, a service you forgot about, a debug port someone left open, or a backdoor an attacker tries to bind are all unreachable from the network unless you deliberately opened them. You go from "everything is exposed unless blocked" to "nothing is exposed unless allowed" — which means new mistakes fail closed. The firewall becomes the backstop for the "minimize services" discipline in the next lesson.

Scope the rules tightly

A good ruleset does not just open a port, it opens it to the right sources. SSH should accept only from your bastion or management network, not the whole internet; an internal API port should accept only from the app tier. nftables lets you match source addresses, so "port 22 from anywhere" becomes "port 22 from 10.0.0.0/24," shrinking exposure even for the ports you must keep open. Combine that with binding services to specific interfaces and you control both who can reach a port and whether it is even listening broadly.

terminal
$ sudo nft -c -f /etc/nftables.conf # -c checks the ruleset without applying it
$ sudo systemctl enable --now nftables # load at boot
$ sudo nft list ruleset # the whole live firewall, readable in one screen
Do not lock yourself out — allow SSH before you drop
Applying a default-drop policy over a live SSH session will cut you off instantly if the rule accepting your own connection is not already in place. Always include the SSH-accept and established-connection rules in the ruleset before you load it, test with nft -c first, and when doing risky firewall changes remotely, use a scheduled rollback (a cron job that flushes the rules in 5 minutes unless you cancel it) so a mistake self-heals instead of stranding you.