Firewall Guide

A firewall controls which packets a Linux host accepts, forwards, or drops. The kernel's Netfilter framework provides the packet-filtering engine; user-space tools -- iptables, nftables, ufw, and firewalld -- write rules into it. This guide covers all four, with enough detail to build a secure ruleset for a server or gateway.

Back to the Networking hub. Related guides: NAT & IP Forwarding | Troubleshooting Tools.

iptables

iptables has been the standard Linux firewall tool for over two decades. Rules are organized into tables (filter, nat, mangle, raw) and chains (INPUT, OUTPUT, FORWARD, PREROUTING, POSTROUTING). The default table is filter.

Basic rule syntax

# Allow incoming SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Allow established and related connections (stateful)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow ICMP (ping)
iptables -A INPUT -p icmp -j ACCEPT

# Allow all traffic on the loopback interface
iptables -A INPUT -i lo -j ACCEPT

# Drop everything else
iptables -A INPUT -j DROP

Source and destination filtering

# Allow HTTP only from a specific subnet
iptables -A INPUT -s 10.0.0.0/24 -p tcp --dport 80 -j ACCEPT

# Block a single IP
iptables -A INPUT -s 203.0.113.99 -j DROP

# Restrict outbound traffic to a specific destination
iptables -A OUTPUT -d 198.51.100.0/24 -p tcp --dport 443 -j ACCEPT

Connection tracking (conntrack)

The --state match (or the newer -m conntrack --ctstate) lets you build stateful rules:

# Modern conntrack syntax
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate NEW -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

ACCEPT, DROP, and REJECT

Target Behaviour
ACCEPT Let the packet through
DROP Silently discard -- the sender gets no reply
REJECT Discard and send an ICMP "port unreachable" back

DROP is usually preferred for inbound traffic from the internet because it gives an attacker no information, while REJECT is friendlier on internal networks since legitimate clients see an immediate error instead of a timeout.

Listing and deleting rules

# List all rules with line numbers
iptables -L -n -v --line-numbers

# Delete rule number 3 from INPUT
iptables -D INPUT 3

# Flush all rules in the filter table
iptables -F

Saving and restoring rules

iptables rules live in kernel memory and vanish on reboot. Persist them:

# Debian / Ubuntu
sudo apt install iptables-persistent
sudo netfilter-persistent save        # saves to /etc/iptables/rules.v4

# RHEL / Fedora
sudo iptables-save > /etc/sysconfig/iptables
sudo systemctl enable iptables

# Manual save / restore
iptables-save  > /root/firewall.rules
iptables-restore < /root/firewall.rules

nftables

nftables is the successor to iptables, offering a cleaner syntax and better performance. Most new distributions ship it by default.

# Create a table and chain
nft add table inet filter
nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; }

# Allow loopback
nft add rule inet filter input iif lo accept

# Allow established / related
nft add rule inet filter input ct state established,related accept

# Allow SSH
nft add rule inet filter input tcp dport 22 accept

# Allow HTTP and HTTPS in one rule
nft add rule inet filter input tcp dport { 80, 443 } accept

# List the ruleset
nft list ruleset

# Save to a file
nft list ruleset > /etc/nftables.conf
sudo systemctl enable nftables

UFW (Uncomplicated Firewall)

UFW is a friendly front-end for iptables, installed by default on Ubuntu:

# Enable UFW (default policy: deny incoming, allow outgoing)
sudo ufw enable

# Allow SSH
sudo ufw allow 22/tcp

# Allow a port range
sudo ufw allow 6000:6100/tcp

# Allow from a specific subnet
sudo ufw allow from 10.0.0.0/24 to any port 3306

# Deny a specific IP
sudo ufw deny from 203.0.113.99

# Show current rules
sudo ufw status verbose

# Delete a rule by number
sudo ufw status numbered
sudo ufw delete 3

# Reset to defaults
sudo ufw reset

firewalld

firewalld is the default on RHEL, CentOS, and Fedora. It uses the concept of zones (public, internal, dmz, trusted, etc.) and services.

# Check the default zone
firewall-cmd --get-default-zone

# List active rules
firewall-cmd --list-all

# Allow a service permanently
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent

# Allow a custom port
firewall-cmd --add-port=8080/tcp --permanent

# Reload to apply permanent changes
firewall-cmd --reload

# Add a rich rule for fine-grained control
firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4"
  source address="10.0.0.0/24"
  port port="5432" protocol="tcp"
  accept'

# Remove a service
firewall-cmd --remove-service=http --permanent
firewall-cmd --reload

Putting it together: a minimal server ruleset

# Flush existing rules
iptables -F
iptables -X

# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Loopback
iptables -A INPUT -i lo -j ACCEPT

# Stateful tracking
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# SSH, HTTP, HTTPS
iptables -A INPUT -p tcp --dport 22  -j ACCEPT
iptables -A INPUT -p tcp --dport 80  -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# ICMP
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# Log and drop everything else
iptables -A INPUT -j LOG --log-prefix "IPT-DROP: " --log-level 4
iptables -A INPUT -j DROP

# Save
netfilter-persistent save