Linux has three firewall layers that often confuse newcomers — they are related but distinct. nftables is the modern kernel packet filtering framework. iptables is the classic interface to the older netfilter framework (still widely used). firewalld is a management daemon that sits on top of either nftables or iptables and provides zones, services, and a simpler management interface.
On RHEL 8+ and Ubuntu 20.04+, firewalld
with nftables underneath is the standard. This page covers firewalld
as the primary tool, with nftables for direct use cases. For iptables see the
dedicated CMDS page.
# Install and enable firewalld sudo dnf install -y firewalld # RHEL sudo apt install -y firewalld # Debian/Ubuntu sudo systemctl enable --now firewalld # Check status sudo firewall-cmd --state sudo systemctl status firewalld # List all zones sudo firewall-cmd --get-zones sudo firewall-cmd --list-all-zones # Show default zone sudo firewall-cmd --get-default-zone # Show active zones and their interfaces sudo firewall-cmd --get-active-zones # Show everything in the default zone sudo firewall-cmd --list-allfirewall-cmd --list-all output:
public = untrusted network (internet-facing default),
trusted = full access allowed,
internal = internal network with some restrictions,
dmz = DMZ servers,
drop = silently drop all incoming.
# List available predefined services sudo firewall-cmd --get-services # Allow a service (runtime only -- lost on reload) sudo firewall-cmd --add-service=http sudo firewall-cmd --add-service=https sudo firewall-cmd --add-service=mysql # Allow permanently (survives reboot) sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --permanent --add-service=https sudo firewall-cmd --permanent --add-service=mysql # Apply permanent changes to runtime sudo firewall-cmd --reload # Or do both at once sudo firewall-cmd --permanent --add-service=https sudo firewall-cmd --reload # Remove a service sudo firewall-cmd --permanent --remove-service=cockpit sudo firewall-cmd --reload # Verify sudo firewall-cmd --list-services
--permanent, changes apply immediately but are lost
on the next firewall-cmd --reload or reboot.
With --permanent, changes persist but don't apply
until reload. Always follow with --reload.
# Allow a specific port sudo firewall-cmd --permanent --add-port=8080/tcp sudo firewall-cmd --permanent --add-port=5432/tcp sudo firewall-cmd --permanent --add-port=53/udp # Allow a port range sudo firewall-cmd --permanent --add-port=8000-8100/tcp # Remove a port sudo firewall-cmd --permanent --remove-port=8080/tcp # List open ports sudo firewall-cmd --list-ports # Apply changes sudo firewall-cmd --reload # Verify everything sudo firewall-cmd --list-all
http and ssh are defined in
/usr/lib/firewalld/services/ and map to their
standard ports. Using service names is cleaner and more readable
than raw ports. Create custom service definitions for non-standard
applications.
Rich rules give you fine-grained control — allow/deny by source IP, rate limiting, logging:
# Allow SSH only from a specific network sudo firewall-cmd --permanent --add-rich-rule=\ 'rule family="ipv4" source address="192.168.1.0/24" service name="ssh" accept' # Block a specific IP entirely sudo firewall-cmd --permanent --add-rich-rule=\ 'rule family="ipv4" source address="203.0.113.42" drop' # Allow HTTP but log it sudo firewall-cmd --permanent --add-rich-rule=\ 'rule family="ipv4" service name="http" log prefix="HTTP: " level="info" accept' # Rate limit SSH connections (3 per minute per IP) sudo firewall-cmd --permanent --add-rich-rule=\ 'rule family="ipv4" service name="ssh" limit value="3/m" accept' # Allow a port from a specific source only sudo firewall-cmd --permanent --add-rich-rule=\ 'rule family="ipv4" source address="10.0.0.0/8" port port="3306" protocol="tcp" accept' # List rich rules sudo firewall-cmd --list-rich-rules # Remove a rich rule sudo firewall-cmd --permanent --remove-rich-rule=\ 'rule family="ipv4" source address="203.0.113.42" drop' sudo firewall-cmd --reload
# Assign an interface to a zone sudo firewall-cmd --permanent --zone=internal --add-interface=ens4 sudo firewall-cmd --permanent --zone=public --add-interface=ens3 # Assign a source IP range to a zone (source-based routing) sudo firewall-cmd --permanent --zone=trusted --add-source=192.168.1.0/24 sudo firewall-cmd --permanent --zone=trusted --add-source=10.0.0.0/8 # Configure internal zone to allow more services sudo firewall-cmd --permanent --zone=internal --add-service=mysql sudo firewall-cmd --permanent --zone=internal --add-service=nfs sudo firewall-cmd --permanent --zone=internal --add-service=samba # Create a custom zone sudo firewall-cmd --permanent --new-zone=management sudo firewall-cmd --permanent --zone=management --add-source=10.10.0.0/24 sudo firewall-cmd --permanent --zone=management --add-service=ssh sudo firewall-cmd --permanent --zone=management --add-service=cockpit # Set the default zone sudo firewall-cmd --set-default-zone=public sudo firewall-cmd --reload sudo firewall-cmd --get-active-zones
trusted or a custom
zone, those IPs get broader access automatically — no need for
individual rich rules per service. Traffic from other sources hits
the public zone with its stricter rules.
# Enable masquerading (NAT) on a zone -- for internet sharing sudo firewall-cmd --permanent --zone=public --add-masquerade sudo firewall-cmd --reload # Verify masquerade is enabled sudo firewall-cmd --zone=public --query-masquerade # Port forwarding -- forward incoming port 8080 to internal host port 80 sudo firewall-cmd --permanent --zone=public \ --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=192.168.1.10 sudo firewall-cmd --reload # Forward a port on this host to a different local port sudo firewall-cmd --permanent --zone=public \ --add-forward-port=port=2222:proto=tcp:toport=22 # List port forwarding rules sudo firewall-cmd --list-forward-ports # Enable IP forwarding (required for masquerade/NAT) sudo sysctl -w net.ipv4.ip_forward=1 echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-forwarding.conf
For systems not using firewalld, or for advanced use cases, nftables is the modern kernel firewall framework:
# Install nftables sudo dnf install -y nftables # RHEL sudo apt install -y nftables # Debian/Ubuntu sudo systemctl enable --now nftables # View current ruleset sudo nft list ruleset # Create a basic ruleset file sudo tee /etc/nftables.conf << 'EOF' #!/usr/sbin/nft -f flush ruleset table inet filter { chain input { type filter hook input priority 0; policy drop; # Allow established and related connections ct state established,related accept # Allow loopback iif lo accept # Allow ICMP ip protocol icmp accept ip6 nexthdr icmpv6 accept # Allow SSH from management network only ip saddr 192.168.1.0/24 tcp dport 22 accept # Allow HTTP and HTTPS tcp dport { 80, 443 } accept # Log and drop everything else log prefix "nft drop: " drop } chain forward { type filter hook forward priority 0; policy drop; } chain output { type filter hook output priority 0; policy accept; } } EOF # Apply the ruleset sudo nft -f /etc/nftables.conf # Verify sudo nft list ruleset
#!/bin/bash
# firewall-audit.sh -- verify firewall state
echo "=== Firewall Audit: $(hostname) ==="
echo "=== $(date) ==="
echo ""
echo "--- FIREWALLD STATUS ---"
sudo firewall-cmd --state 2>/dev/null || echo "firewalld not running"
echo ""
echo "--- ACTIVE ZONES ---"
sudo firewall-cmd --get-active-zones 2>/dev/null
echo ""
echo "--- DEFAULT ZONE RULES ---"
sudo firewall-cmd --list-all 2>/dev/null
echo ""
echo "--- RICH RULES ---"
sudo firewall-cmd --list-rich-rules 2>/dev/null
echo ""
echo "--- LISTENING PORTS (cross-reference with firewall) ---"
sudo ss -tulpn | grep LISTEN
echo ""
echo "--- NFTABLES RULESET ---"
sudo nft list ruleset 2>/dev/null | head -40
echo ""
echo "--- UNEXPECTED OPEN PORTS CHECK ---"
echo "Services allowed by firewall:"
sudo firewall-cmd --list-services 2>/dev/null
echo ""
echo "Ports actually listening:"
sudo ss -tulpn | grep LISTEN | awk '{print $5}' | sed 's/.*://' | sort -n | uniq
| Command | What it does |
|---|---|
| firewall-cmd --state | Is firewalld running? |
| firewall-cmd --list-all | Show all rules in default zone |
| firewall-cmd --get-active-zones | Show zones and their interfaces |
| firewall-cmd --permanent --add-service=http | Allow HTTP permanently |
| firewall-cmd --permanent --remove-service=cockpit | Remove cockpit access |
| firewall-cmd --permanent --add-port=8080/tcp | Allow port 8080 |
| firewall-cmd --permanent --remove-port=8080/tcp | Remove port 8080 |
| firewall-cmd --reload | Apply permanent changes |
| firewall-cmd --list-rich-rules | Show rich rules |
| firewall-cmd --permanent --add-masquerade | Enable NAT/masquerade |
| firewall-cmd --permanent --zone=trusted --add-source=10.0.0.0/8 | Trust a network |
| Tool | Level | Best For | Default On |
|---|---|---|---|
| firewalld | Management layer | Server firewall management, zones, dynamic rules | RHEL 7+, Fedora |
| ufw | Management layer | Simple server firewall management | Ubuntu |
| nftables | Kernel framework | Advanced rulesets, high performance, scripting | RHEL 8+, Debian 10+ |
| iptables | Kernel framework (legacy) | Legacy systems, existing rulesets | Older distros |