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 conntrack --ctstate 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;
        ct state established,related accept
        ct state invalid drop
        iif lo accept
        meta l4proto { icmp, ipv6-icmp } accept
        tcp dport { 22, 443 } accept
    }
}

With policy drop, anything not explicitly accepted is gone - which is why the loopback line (iif lo) and ICMP matter as much as the open ports. Without iif lo accept, services talking to each other over localhost (a web app and its local database, for instance) break; without ICMPv6, IPv6 itself stops working, since neighbor discovery runs over it.

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.