iptables and nftables
iptables and nft: list rules, open and close a port, allowlists, keep rules after reboot, port forwarding, ufw.
Check the result with the rules analyzer: paste the output of iptables-save or nft list ruleset.
What do I have
iptables -V # (nf_tables) — iptables on top of nft; (legacy) — the old one
nft list ruleset | head # nft rules (including those created via iptables-nft)
ufw status verbose # if ufw is installed
firewall-cmd --state # if firewalld is installed
Change rules through one tool: ufw, firewalld or iptables/nft directly. Mixing them is the main source of "the rule is there but does not work".
List rules
iptables -S # as commands
iptables -L INPUT -n -v --line-numbers # as a table, with counters and numbers
iptables -t nat -L -n -v # NAT (Docker, port forwarding)
iptables-save > rules-$(date +%F).txt # everything — for the analyzer and as a backup
ip6tables-save # the same for IPv6 — often forgotten
nft list ruleset
Baseline: everything closed except what you need
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
iptables -P INPUT DROP # last — after the SSH rule
The same in nft:
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iif "lo" accept
ct state established,related accept
tcp dport 22 accept
tcp dport { 80, 443 } accept
}
}
nft -f /etc/nftables.conf && systemctl enable nftables
Open, close, delete
iptables -I INPUT 3 -p tcp --dport 8443 -j ACCEPT # insert as the third rule
iptables -A INPUT -p tcp --dport 6379 -j DROP
iptables -L INPUT --line-numbers # rule number
iptables -D INPUT 5 # delete by number
iptables -D INPUT -p tcp --dport 8443 -j ACCEPT # delete by text
nft -a list chain inet filter input # numbers (handles) in nft
nft delete rule inet filter input handle 12
Order matters: rules are checked top to bottom until the first ACCEPT or DROP. A DROP below a rule that accepts everyone closes nothing.
Allowlist: only from my addresses
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.10 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -s 198.51.100.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP
Many addresses — use a set (ipset with iptables, a set in nft):
ipset create admins hash:net
ipset add admins 203.0.113.10
iptables -A INPUT -p tcp --dport 22 -m set --match-set admins src -j ACCEPT
set admins { type ipv4_addr; flags interval; elements = { 203.0.113.10, 198.51.100.0/24 } }
tcp dport 22 ip saddr @admins accept
Rate limiting (SSH)
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 6 -j DROP
tcp dport 22 ct state new limit rate over 6/minute drop
fail2ban is more convenient: it bans only those who guess passwords. rs harden turns it on.
Keep rules after a reboot
apt install iptables-persistent
netfilter-persistent save # → /etc/iptables/rules.v4 and rules.v6
iptables-restore < /etc/iptables/rules.v4
nft list ruleset > /etc/nftables.conf && systemctl enable nftables
Port forwarding (NAT)
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -p tcp --dport 2222 -j DNAT --to-destination 10.0.0.5:22
iptables -t nat -A POSTROUTING -d 10.0.0.5 -p tcp --dport 22 -j MASQUERADE
iptables -A FORWARD -d 10.0.0.5 -p tcp --dport 22 -j ACCEPT
ufw — the same, shorter
ufw default deny incoming && ufw default allow outgoing
ufw allow 22/tcp
ufw allow from 203.0.113.10 to any port 5432 proto tcp
ufw status numbered && ufw delete 3
Ports published by Docker are not closed by ufw — see Docker and the firewall.
Do not lock yourself out
iptables-save > /root/fw-ok.txt
echo "iptables-restore < /root/fw-ok.txt" | at now + 5 minutes # rollback in 5 minutes
# … change the rules, test a login from a NEW window …
atq && atrm <number> # all good — cancel the rollback
rs harden rolls SSH and the firewall back the same way. As a last resort — the console (VNC) in your provider's panel.
