🛡️ Firewall Management

Security Series: Part 1 — Security Basics  |  Part 2 — SSH Hardening  |  Part 3 — Firewall Management  |  Part 4 — Auditing & Compliance

Linux Firewall Landscape

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.

Examples

1
firewalld Basics — Status and Zones
# 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-all
firewall-cmd --list-all output:
public (active) target: default icmp-block-inversion: no interfaces: ens3 sources: services: cockpit dhcpv6-client ssh ports: protocols: forward: yes masquerade: no rich rules:
firewalld zones explained: A zone defines the trust level for a network interface or source address. public = untrusted network (internet-facing default), trusted = full access allowed, internal = internal network with some restrictions, dmz = DMZ servers, drop = silently drop all incoming.
2
Allow and Remove Services
# 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
💡 Runtime vs Permanent. Without --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.
3
Allow Specific Ports
# 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
Services vs ports: Predefined services like 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.
4
Rich Rules — Advanced Filtering

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
💡 Use rich rules for source IP restrictions. If SSH should only be accessible from your management network, remove the general SSH service and add a rich rule restricting it to your management CIDR. This is more effective than just relying on fail2ban.
5
Zones — Multi-Interface and Source-Based Rules
# 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
Source-based zones are powerful. By assigning your management network IP range to the 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.
6
NAT and Masquerading with firewalld
# 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
7
nftables — Direct Kernel Firewall

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
nftables vs iptables: nftables is the successor to iptables — cleaner syntax, better performance, native IPv4/IPv6 support in one ruleset. On RHEL 8+ and Ubuntu 20.04+, iptables commands are translated to nftables rules transparently. For new deployments use nftables or firewalld directly.
8
Firewall Audit and Verification
#!/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
Cross-reference firewall rules with listening ports. The firewall audit is most valuable when you compare what the firewall allows against what is actually listening. Services that are listening but not in the firewall rules may be accessible to internal hosts. Services in the firewall rules but not listening are dead rules that should be cleaned up.

Quick Reference

firewall-cmd Common Commands

CommandWhat it does
firewall-cmd --stateIs firewalld running?
firewall-cmd --list-allShow all rules in default zone
firewall-cmd --get-active-zonesShow zones and their interfaces
firewall-cmd --permanent --add-service=httpAllow HTTP permanently
firewall-cmd --permanent --remove-service=cockpitRemove cockpit access
firewall-cmd --permanent --add-port=8080/tcpAllow port 8080
firewall-cmd --permanent --remove-port=8080/tcpRemove port 8080
firewall-cmd --reloadApply permanent changes
firewall-cmd --list-rich-rulesShow rich rules
firewall-cmd --permanent --add-masqueradeEnable NAT/masquerade
firewall-cmd --permanent --zone=trusted --add-source=10.0.0.0/8Trust a network

Firewall Tool Comparison

ToolLevelBest ForDefault On
firewalldManagement layerServer firewall management, zones, dynamic rulesRHEL 7+, Fedora
ufwManagement layerSimple server firewall managementUbuntu
nftablesKernel frameworkAdvanced rulesets, high performance, scriptingRHEL 8+, Debian 10+
iptablesKernel framework (legacy)Legacy systems, existing rulesetsOlder distros

← Back to Security Index ↑ Back to EXPANDED