One firewall, not two
Debian 13 ships nftables with an /etc/nftables.conf and a service that loads it. AlmaLinux 10 ships firewalld on top of nftables, and its service reads what /etc/sysconfig/nftables.conf includes. Pick one. Running firewalld alongside a hand-written ruleset produces a ruleset neither of you wrote.
systemctl disable --now firewalld # AlmaLinux, if going manual
systemctl enable --now nftablesThe ruleset
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set ssh_flood {
type ipv4_addr
flags dynamic, timeout
timeout 1h
}
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif lo accept
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } accept
ip6 nexthdr ipv6-icmp accept
tcp dport 22 ct state new add @ssh_flood { ip saddr limit rate over 6/minute } drop
tcp dport 22 accept
tcp dport { 80, 443 } accept
udp dport 443 accept
counter comment "dropped by policy"
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}The parts people get wrong:
- ICMPv6 is accepted wholesale on purpose. Neighbour discovery and packet-too-big both live there, and filtering them by hand breaks IPv6 in ways that present as an application bug for a day and a half.
- The
ssh_floodset adds any address exceeding six new connections a minute to a one-hour timeout list. No log parser, no cron job, no second daemon. - For IPv6, duplicate the set with
type ipv6_addrand the rule withip6 saddr. Sets are single-family. udp dport 443is there for QUIC. Delete it if you do not serve it.
Load it without locking yourself out
systemd-run --on-active=120 nft flush ruleset
nft -f /etc/nftables.confThe first command schedules a transient unit that clears the ruleset in two minutes and prints its name. Open a fresh session. If it works, stop the timer:
systemctl stop run-u1234.timerIf it does not work, wait, and the machine lets you back in by itself. An empty ruleset means no filtering, so finish the job rather than leaving it there overnight.
Check what it is actually doing
nft list ruleset
nft -a list chain inet filter input
nft list set inet filter ssh_flood-a prints handles, which is how a single rule is removed without rewriting the file:
nft delete rule inet filter input handle 12Counters answer the question nobody asks: a rule sitting at zero after a week is either wrong or unnecessary, and both are worth knowing.
Tracing
When a packet refuses to behave the way the ruleset says it should:
nft add rule inet filter input tcp dport 8443 meta nftrace set 1
nft monitor traceDelete the trace rule afterwards. It is cheap rather than free.
What a host firewall is not for
It filters policy, not floods. Volumetric attacks are dropped at the edge long before they reach your port, and a rule on the instance would only be spending your CPU on packets that were already handled. Details on DDoS protection.
Persist it
nft list ruleset > /etc/nftables.conf
systemctl restart nftablesPut the shebang and the flush ruleset line back at the top of the exported file. Without them the next load appends to the running ruleset instead of replacing it, and every rule you have appears twice.