Linux Firewall Management

Complete Reference: iptables & firewalld

Linux firewall management has evolved from the traditional iptables command-line tool to the modern firewalld dynamic firewall daemon. This comprehensive guide covers both tools, their relationship, differences, and how to use each effectively. Understanding both is essential for Linux system administration, as you'll encounter legacy iptables systems and modern firewalld implementations.

Overview and Evolution

The Netfilter Framework

Both iptables and firewalld are frontends to the Linux kernel's netfilter framework, which performs the actual packet filtering. Understanding this layered architecture is crucial:

ℹ️ The Stack
  • Kernel (netfilter): The actual packet filtering engine in the Linux kernel
  • iptables/nftables: Low-level tools to configure netfilter rules
  • firewalld: High-level daemon that manages iptables/nftables dynamically
  • firewall-cmd: Command-line client for firewalld

Historical Evolution

Era Tool Description Status
1994-2001 ipfwadm, ipchains Early Linux firewall tools Obsolete
2001-Present iptables Standard firewall for 15+ years Legacy but still widely used
2011-Present firewalld Dynamic firewall daemon Default in RHEL 7+, Fedora, CentOS
2014-Present nftables Modern replacement for iptables Default in RHEL 9+, Debian 10+

Key Architectural Differences

Aspect iptables firewalld
Nature Static rules, command-line tool Dynamic daemon with runtime/permanent configs
Changes Requires flushing and reloading all rules Changes apply immediately without disruption
Configuration Rule-by-rule, sequential Zone-based with services and rich rules
Persistence Requires separate save/restore mechanism Built-in runtime and permanent configurations
Complexity Steeper learning curve, more control Easier for common tasks, more abstraction
D-Bus Support No Yes - can be controlled via D-Bus
IPv4/IPv6 Separate tools (iptables/ip6tables) Unified management
💡 Which Should You Use?

Use firewalld when:

  • Running RHEL/CentOS 7+, Fedora, or similar distributions
  • You want dynamic rule changes without disruption
  • You prefer zone-based management
  • You're setting up a new system

Use iptables when:

  • Working with legacy systems
  • You need very specific, fine-grained control
  • Working with Debian/Ubuntu (though ufw is common there)
  • Existing infrastructure uses it
  • You need to understand what's happening under the hood

iptables vs firewalld Command Comparison

Common Tasks Side-by-Side

Task iptables Command firewalld Command
Allow SSH iptables -A INPUT -p tcp --dport 22 -j ACCEPT firewall-cmd --add-service=ssh --permanent
Allow HTTP iptables -A INPUT -p tcp --dport 80 -j ACCEPT firewall-cmd --add-service=http --permanent
Allow specific port iptables -A INPUT -p tcp --dport 8080 -j ACCEPT firewall-cmd --add-port=8080/tcp --permanent
Block IP address iptables -A INPUT -s 192.168.1.100 -j DROP firewall-cmd --add-rich-rule='rule family=ipv4 source address=192.168.1.100 drop'
List all rules iptables -L -n -v firewall-cmd --list-all
Save configuration iptables-save > /etc/sysconfig/iptables firewall-cmd --runtime-to-permanent
Restore configuration iptables-restore < /etc/sysconfig/iptables firewall-cmd --reload
Flush all rules iptables -F firewall-cmd --complete-reload
Delete specific rule iptables -D INPUT 3 firewall-cmd --remove-service=http
Enable masquerading (NAT) iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE firewall-cmd --add-masquerade --permanent
Port forwarding iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080 firewall-cmd --add-forward-port=port=80:proto=tcp:toport=8080
Allow from specific interface iptables -A INPUT -i eth1 -j ACCEPT firewall-cmd --zone=trusted --add-interface=eth1
⚠️ Don't Mix Tools!

Do not use iptables and firewalld simultaneously on the same system. They manage the same underlying netfilter rules and will conflict. Choose one approach and stick with it.

iptables Deep Dive

iptables Architecture

iptables organizes rules into tables, which contain chains, which contain individual rules.

Tables

Table Purpose Common Chains
filter Packet filtering (default table) INPUT, FORWARD, OUTPUT
nat Network Address Translation PREROUTING, POSTROUTING, OUTPUT
mangle Packet alteration (QoS, TTL) PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING
raw Connection tracking exemptions PREROUTING, OUTPUT
security SELinux/AppArmor rules INPUT, FORWARD, OUTPUT

Built-in Chains

Chain Description When Traversed
INPUT Packets destined for local system Incoming packets to this host
OUTPUT Packets generated by local system Outgoing packets from this host
FORWARD Packets being routed through system Packets passing through (routing/NAT)
PREROUTING Before routing decision Packets as they arrive, before routing
POSTROUTING After routing decision Packets after routing, before leaving

Common Targets

Target Description
ACCEPT Allow the packet through
DROP Silently discard the packet
REJECT Discard and send error back
LOG Log packet information
MASQUERADE Dynamic source NAT
SNAT Static source NAT
DNAT Destination NAT
REDIRECT Redirect to local port
RETURN Return to calling chain

iptables Command Syntax

iptables [-t table] command [chain] [matching-criteria] -j [target]

Basic iptables Commands

# List rules
iptables -L                    # List all rules (filter table)
iptables -L -n                 # Numeric output (no DNS lookups)
iptables -L -v                 # Verbose (show packet/byte counts)
iptables -L -n -v --line-numbers  # Show line numbers
iptables -t nat -L -n -v       # List NAT table rules

# Append rule to end of chain
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Insert rule at specific position
iptables -I INPUT 1 -p tcp --dport 80 -j ACCEPT

# Delete rule by specification
iptables -D INPUT -p tcp --dport 22 -j ACCEPT

# Delete rule by line number
iptables -D INPUT 3

# Replace rule at line number
iptables -R INPUT 3 -p tcp --dport 443 -j ACCEPT

# Flush all rules in chain
iptables -F INPUT              # Flush INPUT chain
iptables -F                    # Flush all chains

# Flush all rules in table
iptables -t nat -F

# Delete all user-defined chains
iptables -X

# Set default policy
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Create new chain
iptables -N custom_chain

# Delete empty user-defined chain
iptables -X custom_chain

Match Criteria Options

Option Description Example
-p, --protocol Protocol (tcp, udp, icmp, all) -p tcp
-s, --source Source IP/network -s 192.168.1.0/24
-d, --destination Destination IP/network -d 10.0.0.5
-i, --in-interface Input interface -i eth0
-o, --out-interface Output interface -o eth1
--sport Source port --sport 1024:65535
--dport Destination port --dport 80
-m state --state Connection state -m state --state NEW,ESTABLISHED
-m mac --mac-source Source MAC address -m mac --mac-source 00:11:22:33:44:55
-m limit --limit Rate limiting -m limit --limit 5/min
-m multiport --dports Multiple ports -m multiport --dports 22,80,443
-m iprange --src-range IP range -m iprange --src-range 192.168.1.1-192.168.1.50

Practical iptables Examples

Basic Server Firewall

# Set default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Allow loopback
iptables -A INPUT -i lo -j ACCEPT

# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

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

# Allow HTTP and HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

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

# Log dropped packets
iptables -A INPUT -j LOG --log-prefix "iptables-dropped: "

# Save rules (Red Hat/CentOS)
iptables-save > /etc/sysconfig/iptables

# Save rules (Debian/Ubuntu)
iptables-save > /etc/iptables/rules.v4

Web Server with Rate Limiting

# Limit SSH connections from same IP
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent \
  --update --seconds 60 --hitcount 4 -j DROP

# Allow HTTP with connection limit
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 50 -j REJECT

# Rate limit ICMP
iptables -A INPUT -p icmp -m limit --limit 1/s --limit-burst 5 -j ACCEPT
iptables -A INPUT -p icmp -j DROP

Allow Specific Networks

# Allow from office network
iptables -A INPUT -s 10.0.0.0/8 -j ACCEPT

# Allow from multiple networks
iptables -A INPUT -m iprange --src-range 192.168.1.1-192.168.1.100 -j ACCEPT

# Block specific IP
iptables -A INPUT -s 1.2.3.4 -j DROP

# Block entire country (using ipset)
ipset create china hash:net
ipset add china 1.0.1.0/24
ipset add china 1.0.2.0/24
iptables -A INPUT -m set --match-set china src -j DROP

Logging Examples

# Log before dropping
iptables -A INPUT -j LOG --log-prefix "INPUT-dropped: " --log-level 4
iptables -A INPUT -j DROP

# Log new SSH connections
iptables -A INPUT -p tcp --dport 22 -m state --state NEW \
  -j LOG --log-prefix "New SSH: "

# Log with more details
iptables -A INPUT -j LOG --log-prefix "Dropped: " \
  --log-tcp-sequence --log-tcp-options --log-ip-options

Saving and Restoring Rules

# Save current rules
iptables-save > /root/firewall-backup.rules

# Restore rules
iptables-restore < /root/firewall-backup.rules

# Red Hat/CentOS persistent rules
iptables-save > /etc/sysconfig/iptables
systemctl enable iptables   # Make persistent across reboots

# Debian/Ubuntu persistent rules
apt install iptables-persistent
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6

IPv6 with ip6tables

# List IPv6 rules
ip6tables -L -n -v

# Basic IPv6 rules (mirror of IPv4)
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT

# Allow ICMPv6 (important for IPv6)
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT

# Set default policy
ip6tables -P INPUT DROP

# Save IPv6 rules
ip6tables-save > /etc/sysconfig/ip6tables

firewalld Deep Dive

firewalld Architecture

firewalld uses a zone-based model where network interfaces and sources are assigned to zones, and zones define what traffic is allowed.

Key Concepts

  • Zones: Define trust level and allowed services/ports
  • Services: Named groups of ports and protocols
  • Runtime Configuration: Active now, lost on reload
  • Permanent Configuration: Saved to disk, applied on reload
  • Rich Rules: Complex filtering rules
  • Direct Rules: Raw iptables rules (advanced)

Installation and Setup

# Install firewalld (RHEL/CentOS/Fedora)
yum install firewalld

# Start and enable firewalld
systemctl start firewalld
systemctl enable firewalld

# Check status
systemctl status firewalld
firewall-cmd --state

# Check if firewalld is running
firewall-cmd --state

# Get firewalld version
firewall-cmd --version

Runtime vs Permanent Configuration

# Runtime change (lost on reload or reboot)
firewall-cmd --add-service=http

# Permanent change (saved to disk)
firewall-cmd --permanent --add-service=http

# Apply permanent changes
firewall-cmd --reload

# Common pattern: change both runtime and permanent
firewall-cmd --add-service=http
firewall-cmd --permanent --add-service=http

# Or make runtime changes permanent
firewall-cmd --add-service=http
firewall-cmd --runtime-to-permanent
💡 Best Practice

Test changes in runtime first, then make them permanent once verified. This prevents locking yourself out due to misconfiguration.

Basic firewall-cmd Commands

# Get default zone
firewall-cmd --get-default-zone

# Set default zone
firewall-cmd --set-default-zone=public

# List all zones
firewall-cmd --get-zones

# List active zones
firewall-cmd --get-active-zones

# Get zone details
firewall-cmd --list-all
firewall-cmd --zone=public --list-all

# List services in zone
firewall-cmd --list-services
firewall-cmd --zone=public --list-services

# List ports in zone
firewall-cmd --list-ports
firewall-cmd --zone=public --list-ports

# List all information
firewall-cmd --list-all-zones

Managing Services

# Add service
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --reload

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

# Add multiple services at once
firewall-cmd --permanent --add-service={http,https,ssh}
firewall-cmd --reload

# List available services
firewall-cmd --get-services

# Check if service is added
firewall-cmd --query-service=http

Managing Ports

# Add port
firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --add-port=9000-9100/tcp --permanent  # Port range
firewall-cmd --add-port=514/udp --permanent
firewall-cmd --reload

# Remove port
firewall-cmd --remove-port=8080/tcp --permanent
firewall-cmd --reload

# Add multiple ports
firewall-cmd --permanent --add-port={8080/tcp,8443/tcp,9000/tcp}
firewall-cmd --reload

# Query if port is added
firewall-cmd --query-port=8080/tcp

Managing Interfaces and Sources

# Assign interface to zone
firewall-cmd --zone=public --add-interface=eth0 --permanent
firewall-cmd --zone=trusted --add-interface=eth1 --permanent
firewall-cmd --reload

# Remove interface from zone
firewall-cmd --zone=public --remove-interface=eth0 --permanent

# Change zone of interface
firewall-cmd --zone=trusted --change-interface=eth0 --permanent

# Add source (IP or network) to zone
firewall-cmd --zone=trusted --add-source=192.168.1.0/24 --permanent
firewall-cmd --zone=public --add-source=10.0.0.5 --permanent
firewall-cmd --reload

# Remove source
firewall-cmd --zone=trusted --remove-source=192.168.1.0/24 --permanent

# Query interface zone
firewall-cmd --get-zone-of-interface=eth0

# Query source zone
firewall-cmd --get-zone-of-source=192.168.1.5

Reloading and Resetting

# Reload firewall (apply permanent configuration)
firewall-cmd --reload

# Complete reload (breaks active connections)
firewall-cmd --complete-reload

# Reset to defaults (DANGEROUS!)
firewall-cmd --complete-reload

# Panic mode (block ALL traffic)
firewall-cmd --panic-on

# Disable panic mode
firewall-cmd --panic-off

# Query panic mode
firewall-cmd --query-panic

Zones and Trust Levels

Predefined Zones

firewalld includes several predefined zones, ordered from least to most trusted:

Zone Trust Level Default Behavior Use Case
drop Lowest Drop all incoming, no response Maximum security, hostile networks
block Very Low Reject all incoming with icmp-host-prohibited Similar to drop but sends rejection
public Low Selected incoming accepted (ssh, dhcp) Default zone, public networks
external Low-Medium For external side of router/gateway NAT/masquerading enabled
dmz Medium Publicly accessible, limited access DMZ networks, web servers
work Medium-High Trust most systems Work networks
home High Trust most systems Home networks
internal High For internal side of router/gateway Internal networks
trusted Highest Accept all traffic Fully trusted networks

Zone Configuration Details

# View zone configuration
firewall-cmd --zone=public --list-all

# Example output:
# public (active)
#   target: default
#   icmp-block-inversion: no
#   interfaces: eth0
#   sources:
#   services: ssh dhcpv6-client http https
#   ports: 8080/tcp
#   protocols:
#   masquerade: no
#   forward-ports:
#   source-ports:
#   icmp-blocks:
#   rich rules:

Creating Custom Zones

# Create new zone
firewall-cmd --permanent --new-zone=database

# Add description
firewall-cmd --permanent --zone=database \
  --set-description="Database servers zone"

# Set target (default, ACCEPT, REJECT, DROP)
firewall-cmd --permanent --zone=database --set-target=ACCEPT

# Add services to new zone
firewall-cmd --permanent --zone=database --add-service=postgresql
firewall-cmd --permanent --zone=database --add-service=mysql

# Add source network
firewall-cmd --permanent --zone=database --add-source=10.0.0.0/24

# Reload to apply
firewall-cmd --reload

# Delete custom zone
firewall-cmd --permanent --delete-zone=database
firewall-cmd --reload

Zone Selection Logic

firewalld selects zones based on the following priority:

  1. Source match: If packet source IP matches a zone's source, use that zone
  2. Interface match: If packet arrives on an interface assigned to a zone, use that zone
  3. Default zone: If no match, use the default zone
ℹ️ Multiple Zone Assignments

An interface can only belong to one zone, but you can assign multiple source IPs/networks to different zones. This allows fine-grained control over which hosts can access which services.

Service Management

Understanding Services

Services in firewalld are named definitions that specify ports, protocols, and helper modules. This makes firewall management more intuitive than remembering port numbers.

Common Services and Their Ports

Service Name Ports Description
ssh 22/tcp Secure Shell
http 80/tcp HTTP Web Server
https 443/tcp HTTPS Web Server
smtp 25/tcp Mail Transfer
smtps 465/tcp SMTP over SSL
smtp-submission 587/tcp Mail Submission
imap 143/tcp IMAP Mail
imaps 993/tcp IMAP over SSL
pop3 110/tcp POP3 Mail
pop3s 995/tcp POP3 over SSL
dns 53/tcp, 53/udp Domain Name System
ftp 21/tcp File Transfer Protocol
nfs 2049/tcp Network File System
mysql 3306/tcp MySQL Database
postgresql 5432/tcp PostgreSQL Database
samba 137-139/tcp, 445/tcp Samba File Sharing
dhcpv6-client 546/udp DHCPv6 Client

Viewing Service Definitions

# List all available services
firewall-cmd --get-services

# View service details
firewall-cmd --info-service=http

# Example output:
# http
#   ports: 80/tcp
#   protocols:
#   source-ports:
#   modules:
#   destination:

# Service definition files
ls /usr/lib/firewalld/services/
cat /usr/lib/firewalld/services/http.xml

Creating Custom Services

# Create new service from scratch
firewall-cmd --permanent --new-service=myapp

# Set short name and description
firewall-cmd --permanent --service=myapp --set-short=MyApp
firewall-cmd --permanent --service=myapp \
  --set-description="My Custom Application"

# Add ports to service
firewall-cmd --permanent --service=myapp --add-port=8080/tcp
firewall-cmd --permanent --service=myapp --add-port=8443/tcp

# Add protocol (if needed)
firewall-cmd --permanent --service=myapp --add-protocol=igmp

# View the service
firewall-cmd --info-service=myapp

# Add service to zone
firewall-cmd --permanent --zone=public --add-service=myapp
firewall-cmd --reload

# Delete custom service
firewall-cmd --permanent --delete-service=myapp

Custom Service XML Example

Service definitions are stored as XML files:

/etc/firewalld/services/myapp.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>MyApp</short>
  <description>My Custom Application Service</description>
  <port protocol="tcp" port="8080"/>
  <port protocol="tcp" port="8443"/>
  <port protocol="udp" port="9000-9100"/>
</service>
# After creating XML file, reload
firewall-cmd --reload

# Verify service is available
firewall-cmd --get-services | grep myapp

Rich Rules and Advanced Filtering

Understanding Rich Rules

Rich rules provide more complex filtering beyond simple services and ports. They support:

  • Source/destination filtering
  • Logging
  • Limits and rate limiting
  • Actions (accept, reject, drop)
  • Protocol-specific matching

Rich Rule Syntax

rule [family="ipv4|ipv6"]
     [source address="address[/mask]" [invert="true"]]
     [destination address="address[/mask]" [invert="true"]]
     [service name="service"]
     [port port="portid[-portid]" protocol="tcp|udp"]
     [protocol value="protocol"]
     [icmp-block name="icmptype"]
     [masquerade]
     [forward-port port="portid[-portid]" protocol="tcp|udp"
                   to-port="portid[-portid]" to-addr="address"]
     [log [prefix="prefix"] [level="emerg|alert|crit|err|warn|notice|info|debug"]
          [limit value="rate/duration"]]
     [audit [limit value="rate/duration"]]
     [accept|reject [type="reject-type"]|drop]

Rich Rule Examples

Source-based Filtering

# Allow SSH from specific IP
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  source address="192.168.1.100" \
  service name="ssh" accept'

# Block specific IP
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  source address="1.2.3.4" drop'

# Allow network access
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  source address="10.0.0.0/8" accept'

# Reject with message
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  source address="192.168.1.0/24" \
  service name="http" \
  reject type="icmp-host-prohibited"'

Port-based Filtering with Sources

# Allow port 8080 only from specific network
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  source address="192.168.1.0/24" \
  port port="8080" protocol="tcp" accept'

# Allow port range from specific IP
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  source address="10.0.0.5" \
  port port="9000-9100" protocol="tcp" accept'

Logging

# Log all dropped packets
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  log prefix="FIREWALL-DROPPED: " level="info" drop'

# Log SSH connections with limit
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  service name="ssh" \
  log prefix="SSH-ACCESS: " level="notice" limit value="3/m" \
  accept'

# Log and accept from specific source
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  source address="192.168.1.100" \
  log prefix="ADMIN-ACCESS: " level="info" \
  accept'

Rate Limiting

# Limit SSH connections
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  service name="ssh" \
  accept limit value="3/m"'

# Limit HTTP connections per minute
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  service name="http" \
  accept limit value="100/m"'

# Connection limit with logging
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  port port="8080" protocol="tcp" \
  log prefix="RATE-LIMIT: " level="warn" limit value="5/m" \
  drop'

Protocol-specific Rules

# Allow ICMP from specific network
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  source address="192.168.1.0/24" \
  protocol value="icmp" accept'

# Block IGMP protocol
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  protocol value="igmp" drop'

Managing Rich Rules

# List rich rules
firewall-cmd --list-rich-rules
firewall-cmd --zone=public --list-rich-rules

# Remove rich rule (exact match required)
firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" \
  source address="1.2.3.4" drop'

# Query rich rule
firewall-cmd --query-rich-rule='rule family="ipv4" \
  source address="192.168.1.100" accept'

# Reload to apply
firewall-cmd --reload

NAT and Port Forwarding

Masquerading (Source NAT)

Masquerading allows hosts on a private network to access the internet through the firewall.

iptables

# Enable IP forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

# Make permanent
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

# Enable masquerading
iptables -t nat -A POSTROUTING \
  -o eth0 -j MASQUERADE

# Forward traffic
iptables -A FORWARD -i eth1 \
  -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 \
  -o eth1 -m state \
  --state RELATED,ESTABLISHED \
  -j ACCEPT

firewalld

# Enable masquerading on zone
firewall-cmd --zone=external \
  --add-masquerade --permanent

# Or using rich rule
firewall-cmd --permanent \
  --add-rich-rule='rule family="ipv4" \
  masquerade'

# Check if masquerading is enabled
firewall-cmd --zone=external \
  --query-masquerade

# Disable masquerading
firewall-cmd --zone=external \
  --remove-masquerade --permanent

firewall-cmd --reload

Port Forwarding

iptables

# Forward port 80 to 8080 (local)
iptables -t nat -A PREROUTING \
  -p tcp --dport 80 \
  -j REDIRECT --to-port 8080

# Forward to different host
iptables -t nat -A PREROUTING \
  -p tcp -d 1.2.3.4 --dport 80 \
  -j DNAT --to-destination 192.168.1.10:8080

# Forward traffic
iptables -A FORWARD \
  -p tcp -d 192.168.1.10 \
  --dport 8080 -j ACCEPT

firewalld

# Forward port 80 to 8080 (local)
firewall-cmd --permanent \
  --add-forward-port=port=80:proto=tcp:toport=8080

# Forward to different host
firewall-cmd --permanent \
  --add-forward-port=port=80:proto=tcp:\
toport=8080:toaddr=192.168.1.10

# Using rich rule for more control
firewall-cmd --permanent \
  --add-rich-rule='rule family="ipv4" \
  forward-port port="80" protocol="tcp" \
  to-port="8080" to-addr="192.168.1.10"'

firewall-cmd --reload

Complete NAT Gateway Example

firewalld NAT Gateway Configuration
# Assign interfaces to zones
firewall-cmd --permanent --zone=external --add-interface=eth0  # Internet
firewall-cmd --permanent --zone=internal --add-interface=eth1  # LAN

# Enable masquerading on external zone
firewall-cmd --permanent --zone=external --add-masquerade

# Allow services on internal zone
firewall-cmd --permanent --zone=internal --add-service=ssh
firewall-cmd --permanent --zone=internal --add-service=dns
firewall-cmd --permanent --zone=internal --add-service=dhcp

# Port forward SSH from external to internal host
firewall-cmd --permanent --zone=external \
  --add-forward-port=port=2222:proto=tcp:toport=22:toaddr=192.168.1.10

# Port forward HTTP to web server
firewall-cmd --permanent --zone=external \
  --add-forward-port=port=80:proto=tcp:toport=80:toaddr=192.168.1.20

# Apply changes
firewall-cmd --reload

# Verify configuration
firewall-cmd --zone=external --list-all
firewall-cmd --zone=internal --list-all

Logging and Monitoring

firewalld Logging

# Enable logging for denied packets
firewall-cmd --set-log-denied=all --permanent

# Options: all, unicast, broadcast, multicast, off
firewall-cmd --set-log-denied=unicast --permanent

# Check current setting
firewall-cmd --get-log-denied

# Log using rich rules (more control)
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  log prefix="FIREWALL-DENIED: " level="info" drop'

# Reload
firewall-cmd --reload

# View logs
journalctl -f -u firewalld
journalctl -u firewalld --since "1 hour ago"
grep FIREWALL /var/log/messages

iptables Logging

# Log dropped packets
iptables -A INPUT -j LOG --log-prefix "iptables-INPUT-dropped: " --log-level 4

# Log before accepting
iptables -A INPUT -p tcp --dport 22 -m state --state NEW \
  -j LOG --log-prefix "SSH-NEW: " --log-level 6
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Log with detailed information
iptables -A INPUT -j LOG \
  --log-prefix "INPUT-dropped: " \
  --log-tcp-sequence \
  --log-tcp-options \
  --log-ip-options

# Rate limit logs
iptables -A INPUT -m limit --limit 5/min --limit-burst 10 \
  -j LOG --log-prefix "iptables-dropped: "
iptables -A INPUT -j DROP

# View logs
tail -f /var/log/messages | grep iptables
journalctl -f | grep iptables

Monitoring Firewall Activity

# Real-time firewalld logs
journalctl -u firewalld -f

# Show firewall-related kernel messages
dmesg | grep -i firewall
dmesg -T | grep -i "IN=\|OUT="

# Monitor connections
watch -n 1 'conntrack -L'

# Show connection tracking table
cat /proc/net/nf_conntrack

# Count connections per state
conntrack -L -o extended | awk '{print $4}' | sort | uniq -c

# Show firewall statistics
# iptables
iptables -L -v -n

# firewalld (shows underlying iptables)
iptables -L -v -n
firewall-cmd --direct --get-all-rules

Migrating from iptables to firewalld

Migration Strategy

  1. Document current iptables rules
  2. Map rules to firewalld concepts (zones, services, rich rules)
  3. Test in development/staging environment
  4. Create firewalld configuration
  5. Disable iptables service
  6. Enable firewalld service
  7. Verify and monitor

Step-by-Step Migration

Step 1: Document Current Configuration

# Save current iptables rules
iptables-save > /root/iptables-backup.rules
ip6tables-save > /root/ip6tables-backup.rules

# Document in human-readable format
iptables -L -n -v > /root/iptables-rules.txt

# Note all open ports and services
iptables -L INPUT -n | grep ACCEPT

Step 2: Stop iptables Service

# Stop and disable iptables
systemctl stop iptables
systemctl disable iptables
systemctl stop ip6tables
systemctl disable ip6tables

# Or mask to prevent accidental start
systemctl mask iptables
systemctl mask ip6tables

Step 3: Install and Configure firewalld

# Install firewalld
yum install firewalld

# Start firewalld
systemctl start firewalld
systemctl enable firewalld

# Check status
firewall-cmd --state

Step 4: Recreate Rules in firewalld

# Example: Migrate web server rules

# Old iptables rules:
# 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
# iptables -A INPUT -s 192.168.1.0/24 -j ACCEPT
# iptables -A INPUT -s 10.0.0.5 -p tcp --dport 3306 -j ACCEPT

# New firewalld configuration:
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https

# Trusted network
firewall-cmd --permanent --zone=trusted --add-source=192.168.1.0/24

# Database access for specific host
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  source address="10.0.0.5" \
  service name="mysql" accept'

# Apply changes
firewall-cmd --reload

Step 5: Test and Verify

# Verify configuration
firewall-cmd --list-all
firewall-cmd --list-all-zones

# Test services
nmap localhost
telnet localhost 80
telnet localhost 443

# Check from remote host
ssh user@server
curl http://server

# Monitor logs for issues
journalctl -u firewalld -f

Complex Rule Migration Examples

Example 1: SSH Rate Limiting

iptables

iptables -A INPUT -p tcp \
  --dport 22 -m state \
  --state NEW -m recent --set

iptables -A INPUT -p tcp \
  --dport 22 -m state \
  --state NEW -m recent \
  --update --seconds 60 \
  --hitcount 4 -j DROP

firewalld

firewall-cmd --permanent \
  --add-rich-rule='rule family="ipv4" \
  service name="ssh" \
  accept limit value="3/m"'

firewall-cmd --reload

Example 2: Multi-interface Router

iptables

# Accept on eth1 (internal)
iptables -A INPUT -i eth1 \
  -j ACCEPT

# NAT on eth0 (external)
iptables -t nat -A POSTROUTING \
  -o eth0 -j MASQUERADE

# Forward between interfaces
iptables -A FORWARD \
  -i eth1 -o eth0 -j ACCEPT
iptables -A FORWARD \
  -i eth0 -o eth1 \
  -m state --state \
  ESTABLISHED,RELATED \
  -j ACCEPT

firewalld

# Assign interfaces to zones
firewall-cmd --permanent \
  --zone=internal \
  --add-interface=eth1

firewall-cmd --permanent \
  --zone=external \
  --add-interface=eth0

# Enable masquerading
firewall-cmd --permanent \
  --zone=external \
  --add-masquerade

firewall-cmd --reload
💡 Migration Tips
  • Test each rule after migration
  • Use runtime configuration first, then make permanent
  • Keep iptables backup until verified
  • Document why each rule exists
  • Consider using firewalld's zone model rather than directly translating rules
  • Some complex iptables rules may need rich rules or direct rules in firewalld

Troubleshooting

Common Issues

Issue: Service Not Accessible After Firewall Changes

# Check if service is running
systemctl status httpd

# Check if service is listening
ss -tlnp | grep :80
netstat -tlnp | grep :80

# Check firewall rules (firewalld)
firewall-cmd --list-all
firewall-cmd --list-services
firewall-cmd --list-ports

# Check firewall rules (iptables)
iptables -L -n -v
iptables -L INPUT -n -v | grep 80

# Test from localhost
curl http://localhost

# Test from remote (if accessible)
telnet server_ip 80
nmap -p 80 server_ip

# Check logs
journalctl -u firewalld -f
tail -f /var/log/messages | grep -i firewall

Issue: Locked Out After Firewall Changes

# Prevention: Always have console/IPMI access

# If using firewalld:
# Access via console and add SSH back
firewall-cmd --add-service=ssh
firewall-cmd --reload

# If using iptables:
# Access via console
iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT
iptables-save > /etc/sysconfig/iptables

# Emergency: Disable firewall temporarily
systemctl stop firewalld
# or
systemctl stop iptables

# Fix rules, then restart
systemctl start firewalld

Issue: firewalld and iptables Conflicting

# Check what's running
systemctl status firewalld
systemctl status iptables

# Disable one (choose one approach)
systemctl stop iptables
systemctl disable iptables
systemctl mask iptables

# Or disable firewalld and use iptables
systemctl stop firewalld
systemctl disable firewalld
systemctl mask firewalld

Issue: Rules Not Persisting After Reboot

# firewalld: Use --permanent flag
firewall-cmd --permanent --add-service=http
firewall-cmd --reload

# iptables: Save rules
iptables-save > /etc/sysconfig/iptables
systemctl enable iptables  # Enable service to restore on boot

# Verify service is enabled
systemctl is-enabled firewalld
systemctl is-enabled iptables

Diagnostic Commands

firewalld Diagnostics

# Check firewalld state
firewall-cmd --state
systemctl status firewalld

# List everything
firewall-cmd --list-all
firewall-cmd --list-all-zones

# Check zone assignments
firewall-cmd --get-active-zones
firewall-cmd --get-default-zone

# View underlying iptables rules
iptables -L -n -v
iptables -t nat -L -n -v

# Check for errors in configuration
journalctl -u firewalld -p err

# Test rule matching
firewall-cmd --query-service=http
firewall-cmd --query-port=8080/tcp

# Reload configuration
firewall-cmd --reload

# Complete reload (breaks connections)
firewall-cmd --complete-reload

iptables Diagnostics

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

# Check specific chain
iptables -L INPUT -n -v
iptables -L FORWARD -n -v

# Show packet/byte counters
iptables -L -n -v -x

# Check if packets are hitting rules
# (watch counter increase)
watch -n 1 'iptables -L INPUT -n -v'

# Reset counters
iptables -Z

# Check connection tracking
conntrack -L
cat /proc/net/nf_conntrack

# Check kernel modules
lsmod | grep -E "ip_tables|nf_"

# Verify IP forwarding
cat /proc/sys/net/ipv4/ip_forward
sysctl net.ipv4.ip_forward

Testing Firewall Rules

# Test from localhost
telnet localhost 80
nc -zv localhost 80
curl http://localhost

# Test from another server
nmap -p 22,80,443 server_ip
telnet server_ip 80

# Test UDP port
nc -u -zv server_ip 53

# Test with specific source IP (if routing allows)
telnet -b source_ip server_ip 80

# Comprehensive port scan
nmap -sS -p- server_ip

# Test ICMP
ping server_ip

# Trace route
traceroute server_ip
mtr server_ip

Logging for Troubleshooting

# Enable verbose firewalld logging
firewall-cmd --set-log-denied=all

# Add logging rule to see what's being blocked
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
  log prefix="FW-BLOCK: " level="info" drop'
firewall-cmd --reload

# Watch logs in real-time
journalctl -u firewalld -f
tail -f /var/log/messages | grep -i firewall

# iptables logging
iptables -I INPUT 1 -j LOG --log-prefix "FW-DEBUG: " --log-level 7

# Watch iptables logs
tail -f /var/log/messages | grep FW-DEBUG
dmesg -w | grep FW-DEBUG

# After troubleshooting, remove debug logging
firewall-cmd --set-log-denied=off
iptables -D INPUT 1

Performance Issues

# Check number of rules (many rules = slower)
iptables -L | wc -l
firewall-cmd --list-all-zones | wc -l

# Check connection tracking table size
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

# Increase connection tracking table if needed
echo 262144 > /proc/sys/net/netfilter/nf_conntrack_max
# Make permanent:
echo "net.netfilter.nf_conntrack_max = 262144" >> /etc/sysctl.conf

# Monitor firewall CPU usage
top -p $(pgrep firewalld)
ps aux | grep firewalld
⚠️ Emergency Access

Always maintain alternate access methods (console, IPMI, serial) before making firewall changes. Test rules before making them permanent. Keep documentation of working configurations.

Quick Reference

Essential Commands

Task iptables firewalld
List rules iptables -L -n -v firewall-cmd --list-all
Add service iptables -A INPUT -p tcp --dport 80 -j ACCEPT firewall-cmd --add-service=http --permanent
Block IP iptables -A INPUT -s 1.2.3.4 -j DROP firewall-cmd --add-rich-rule='rule source address=1.2.3.4 drop'
Save config iptables-save > /etc/sysconfig/iptables firewall-cmd --reload
Flush rules iptables -F firewall-cmd --complete-reload
Enable NAT iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE firewall-cmd --add-masquerade --permanent

← Back to LAMPS Index ↑ Back to EXPANDED