Both tools configure netfilter, the packet-filtering framework built into the Linux kernel - they're interfaces to the same underlying mechanism, not competing implementations of separate ones.

iptables: the long-standing standard

iptables organizes rules into chains (INPUT, OUTPUT, FORWARD for the filter table) processed in order, with a default policy for what happens when nothing matches:

sudo iptables -L -v -n
Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:22
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:443

A minimal example allowing SSH and HTTPS, dropping everything else inbound:

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -P INPUT DROP

That ESTABLISHED,RELATED line matters: without it, reply traffic for connections you initiated outbound gets dropped too, since each packet is otherwise evaluated independently of any connection state.

Rules set this way don't survive a reboot on their own - persist them with a distro-specific mechanism (iptables-persistent on Debian/Ubuntu, saving to /etc/sysconfig/iptables and enabling the iptables service on older RHEL-family systems).

nftables: the successor

nftables replaces iptables (and the separate ip6tables, arptables, ebtables tools it required for IPv6 and non-IP protocols) with one unified framework and syntax. It's the default backend on current Debian, Ubuntu, Fedora, and RHEL - iptables commands on these systems are often translated to nftables rules underneath via a compatibility layer (iptables-nft), rather than using the original kernel implementation directly.

sudo nft list ruleset
table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        tcp dport 22 accept
        tcp dport 443 accept
        ct state established,related accept
    }
}

nftables rules are typically written to a config file (/etc/nftables.conf) and loaded as a set, rather than built up one imperative command at a time the way iptables commands usually are - closer to declaring the desired ruleset than scripting it.

Frontends most distros actually put in front of this

Writing raw rules directly is uncommon day-to-day - most distributions ship a simpler management layer:

  • ufw (Uncomplicated Firewall) - the default on Ubuntu, wraps iptables/nftables with a much simpler syntax:

    sudo ufw allow 22/tcp
    sudo ufw allow ssh
    sudo ufw enable
    sudo ufw status verbose
    
  • firewalld - the default on Fedora and RHEL, organizes rules into named zones (public, home, internal, …) representing trust levels, with a runtime vs. permanent configuration distinction:

    sudo firewall-cmd --add-service=ssh --permanent
    sudo firewall-cmd --add-service=https --permanent
    sudo firewall-cmd --reload
    sudo firewall-cmd --list-all
    

    The --permanent flag matters - without it, a change applies only to the current runtime configuration and is lost on the next reload or reboot.

Checking what's actually active

Before assuming a connectivity problem is a firewall rule, confirm which tool is even managing it:

sudo ufw status
sudo firewall-cmd --state
sudo nft list ruleset

See Troubleshooting Connectivity for where firewall checks fit into a broader diagnostic process, and Hardening Basics for firewall configuration as part of baseline server hardening.