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.
Table of Contents
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:
- 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 |
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 |
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
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:
- Source match: If packet source IP matches a zone's source, use that zone
- Interface match: If packet arrives on an interface assigned to a zone, use that zone
- Default zone: If no match, use the default zone
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:
<?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
# 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
- Document current iptables rules
- Map rules to firewalld concepts (zones, services, rich rules)
- Test in development/staging environment
- Create firewalld configuration
- Disable iptables service
- Enable firewalld service
- 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
- 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
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 |