Complete reference for managing firewalld via command line. Use --permanent to persist changes across reboot.
๐ฅ STATE & RELOAD#
Check Firewall Status#
# Check if firewalld is running
firewall-cmd --state
# Output: running | not running
# Check if firewalld is enabled at boot
systemctl is-enabled firewalld
# Start/stop firewalld
systemctl start firewalld
systemctl stop firewalld
systemctl restart firewalld
Reload Configuration#
# Reload firewall (drop active connections)
firewall-cmd --reload
# Reload firewall (keep active connections alive)
firewall-cmd --complete-reload
# Apply all permanent changes immediately
firewall-cmd --permanent --list-all
firewall-cmd --reload
๐ ZONE MANAGEMENT#
List Zones#
# Get default zone
firewall-cmd --get-default-zone
# Get active zones (zones with rules applied)
firewall-cmd --get-active-zones
# List all available zones
firewall-cmd --get-zones
# Expected output for standard RHEL:
# block dmz drop external home internal public trusted work
Switch Zone#
# Set default zone
firewall-cmd --set-default-zone=public
# Add interface to zone (runtime)
firewall-cmd --zone=public --add-interface=eth0
# Add interface to zone (permanent)
firewall-cmd --permanent --zone=public --add-interface=eth0
# Remove interface from zone
firewall-cmd --permanent --zone=public --remove-interface=eth0
Zone Details#
# List all rules in zone
firewall-cmd --zone=public --list-all
# List services in zone
firewall-cmd --zone=public --list-services
# List ports in zone
firewall-cmd --zone=public --list-ports
# List rich rules in zone
firewall-cmd --zone=public --list-rich-rules
# List direct rules in zone
firewall-cmd --direct --get-all-rules
๐ PORTS - Allow/Block#
Allow Port (Incoming)#
# Allow HTTP (port 80)
firewall-cmd --permanent --zone=public --add-port=80/tcp
firewall-cmd --reload
# Allow HTTPS (port 443)
firewall-cmd --permanent --zone=public --add-port=443/tcp
# Allow SSH (port 22)
firewall-cmd --permanent --zone=public --add-port=22/tcp
# Allow port range
firewall-cmd --permanent --zone=public --add-port=5900-5910/tcp
# Both TCP and UDP
firewall-cmd --permanent --zone=public --add-port=53/tcp
firewall-cmd --permanent --zone=public --add-port=53/udp
Block Port (Outgoing)#
# Block outgoing SMTP from specific zone
firewall-cmd --permanent --zone=drop --add-port=25/tcp
# More specific: use direct rules (see DIRECT RULES section below)
Remove Port#
# Remove allowed port
firewall-cmd --permanent --zone=public --remove-port=80/tcp
firewall-cmd --reload
# List ports to verify
firewall-cmd --zone=public --list-ports
๐ก๏ธ SERVICES - Allow/Block#
Allow Service#
# Allow HTTP service
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --reload
# Allow HTTPS service
firewall-cmd --permanent --zone=public --add-service=https
# Allow Samba (file sharing)
firewall-cmd --permanent --zone=public --add-service=samba
# Allow MariaDB
firewall-cmd --permanent --zone=public --add-service=mysql
# Allow SSH
firewall-cmd --permanent --zone=public --add-service=ssh
View Available Services#
# List all available services
firewall-cmd --get-services | tr ' ' '\n' | sort
# Get ports for specific service
firewall-cmd --service-ports=http
# Output: 80/tcp
# Get protocol for service
firewall-cmd --service-protocols=ssh
# Output: tcp
Remove Service#
firewall-cmd --permanent --zone=public --remove-service=http
firewall-cmd --reload
๐ RICH RULES (Modern Approach)#
Rich rules are more powerful than simple port rules. Recommended over --direct.
Basic Rich Rule Syntax#
firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" protocol value="tcp" port protocol="tcp" port="22" accept'
Allow from Specific Source#
# Allow SSH from specific IP
firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family="ipv4" source address="203.0.113.42" port protocol="tcp" port="22" accept'
# Allow HTTP from specific subnet
firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="http" accept'
Block from Specific Source#
# Block SMTP from private network
firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family="ipv4" source address="10.88.0.0/24" port protocol="tcp" port="25" reject'
# Block SMTP 465 (submissions)
firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family="ipv4" source address="10.88.0.0/24" port protocol="tcp" port="465" reject'
# Block SMTP 587 (submission)
firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family="ipv4" source address="10.88.0.0/24" port protocol="tcp" port="587" reject'
Allow Specific Destination#
# Allow traffic TO specific destination
firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family="ipv4" destination address="192.168.1.100" port protocol="tcp" port="3306" accept'
List Rich Rules#
# List rich rules in zone
firewall-cmd --zone=public --list-rich-rules
# List all rich rules across all zones
for zone in $(firewall-cmd --get-zones); do
echo "Zone: $zone"
firewall-cmd --zone=$zone --list-rich-rules
done
Remove Rich Rule#
# Remove rule (must match exactly)
firewall-cmd --permanent --zone=public \
--remove-rich-rule='rule family="ipv4" source address="10.88.0.0/24" port protocol="tcp" port="25" reject'
๐ DIRECT RULES (Legacy - Use Rich Rules Instead)#
Direct rules interact directly with iptables/nftables. Prefer rich rules for new configurations.
Add Direct Rule#
# Block FORWARD (prevent routing) from source to port
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 -s 10.88.0.0/24 -p tcp --dport 465 -j DROP
# Explanation:
# ipv4 = IPv4 rules
# filter = Packet filter table
# FORWARD = Forward chain (routing)
# 0 = Priority (lower = higher priority)
# -s 10.88.0.0/24 = Source address
# -p tcp = Protocol TCP
# --dport 465 = Destination port
# -j DROP = Jump to DROP (discard packet)
Multiple Ports (SMTP)#
# Block outgoing SMTP (25, 465, 587) from private subnet
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 -s 10.88.0.0/24 -p tcp --dport 25 -j DROP
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 -s 10.88.0.0/24 -p tcp --dport 465 -j DROP
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 -s 10.88.0.0/24 -p tcp --dport 587 -j DROP
List Direct Rules#
# Get all direct rules
firewall-cmd --direct --get-all-rules
# Get all chains
firewall-cmd --direct --get-all-chains
# Get all passthrough rules
firewall-cmd --direct --get-all-passthroughs
Remove Direct Rule#
# Remove rule (must match exactly)
firewall-cmd --permanent --direct \
--remove-rule ipv4 filter FORWARD 0 -s 10.88.0.0/24 -p tcp --dport 25 -j DROP
๐ SOURCE-BASED RULES#
Trust/Block Entire Source#
# Trust all traffic from source (accept anything)
firewall-cmd --permanent --zone=trusted --add-source=192.168.1.0/24
firewall-cmd --reload
# Block all traffic from source (drop anything)
firewall-cmd --permanent --zone=drop --add-source=203.0.113.0/24
firewall-cmd --reload
# List sources in zone
firewall-cmd --zone=drop --list-sources
Remove Source#
firewall-cmd --permanent --zone=trusted --remove-source=192.168.1.0/24
๐ค MASQUERADING & FORWARDING#
Enable IP Masquerade (NAT)#
# Enable masquerading (hide internal IPs behind firewall IP)
firewall-cmd --permanent --zone=external --add-masquerade
firewall-cmd --reload
# Check if enabled
firewall-cmd --zone=external --query-masquerade
# Output: yes | no
Port Forwarding#
# Forward external port 8080 to internal 80
firewall-cmd --permanent --zone=public \
--add-forward-port=port=8080:proto=tcp:toport=80:toaddr=192.168.1.100
# Forward with no address change (same machine)
firewall-cmd --permanent --zone=public \
--add-forward-port=port=8080:proto=tcp:toport=80
# List forwards
firewall-cmd --zone=public --list-forward-ports
# Remove forward
firewall-cmd --permanent --zone=public \
--remove-forward-port=port=8080:proto=tcp:toport=80:toaddr=192.168.1.100
๐ VIRTUALIZOR NAT VPS SETUP#
Context: Virtualizor-Based Container Networks#
Physical Node (Virtualizor KVM/OpenVZ Host)
โโ Management IP: 203.0.113.42 (public)
โโ Internal Bridge: virbr0 (10.88.0.0/24)
โโ VPS Containers:
โโ Container 1 (10.88.0.10)
โโ Container 2 (10.88.0.11)
โโ Container 3 (10.88.0.12)
External clients โ Node public IP โ HAProxy โ Container IPs
Basic Virtualizor NAT Setup#
# 1. Enable masquerading on external zone
firewall-cmd --permanent --zone=external --add-masquerade
firewall-cmd --reload
# 2. Check internal bridge interface
ip addr show virbr0
# Should show: inet 10.88.0.1/24
# 3. Allow forwarding between interfaces
firewall-cmd --permanent --add-masquerade
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 -i virbr0 -j ACCEPT
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 -o virbr0 -j ACCEPT
firewall-cmd --reload
# 4. Verify NAT is working
iptables -t nat -L -n | grep MASQUERADE
Port Forwarding for VPS (No External IP)#
# Forward port 8080 on public IP to internal VPS port 80
firewall-cmd --permanent --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=10.88.0.10
firewall-cmd --reload
# Forward multiple ports
firewall-cmd --permanent --add-forward-port=port=8443:proto=tcp:toport=443:toaddr=10.88.0.10
firewall-cmd --permanent --add-forward-port=port=8022:proto=tcp:toport=22:toaddr=10.88.0.10
# Verify
firewall-cmd --list-forward-ports
HAProxy-Based Routing#
# If using HAProxy as reverse proxy on node:
# HAProxy listens 0.0.0.0:80, 0.0.0.0:443
# HAProxy forwards to backend containers
# Allow HAProxy to forward to containers
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-i br0 \
-d 10.88.0.0/24 \
-j ACCEPT
# Allow containers to establish connections
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-s 10.88.0.0/24 \
-o br0 \
-m conntrack \
--ctstate ESTABLISHED,RELATED \
-j ACCEPT
firewall-cmd --reload
DNS/DHCP for Internal Containers#
# Allow DHCP from containers (port 67/68)
firewall-cmd --permanent --zone=internal --add-service=dhcp
firewall-cmd --permanent --zone=internal --add-service=dns
# Or via direct rules
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-i virbr0 \
-p udp \
--dport 53 \
-j ACCEPT
firewall-cmd --reload
โฑ๏ธ RATE LIMITING (DoS/Brute-Force Protection)#
Rate Limiting Basics#
# Limit new connections to 10 per minute with burst of 5
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 22 \
-m limit \
--limit 10/minute \
--limit-burst 5 \
-j ACCEPT
# Explanation:
# -m limit = Use limit module
# --limit 10/minute = Allow max 10 packets per minute
# --limit-burst 5 = Allow burst of 5 before rate limit kicks in
# -j ACCEPT = Accept matching packets
Rate Limit SSH (Brute-Force Protection)#
# Method 1: Limit connections per minute
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 22 \
-m state \
--state NEW \
-m limit \
--limit 5/minute \
--limit-burst 10 \
-j ACCEPT
# Method 2: Limit per source IP (better for multi-user)
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 22 \
-m state \
--state NEW \
-m hashlimit \
--hashlimit-name SSH-limit \
--hashlimit 3/minute \
--hashlimit-burst 10 \
-j ACCEPT
# Drop anything over limit
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 1 \
-p tcp \
--dport 22 \
-j DROP
Rate Limit HTTP (API/Web Service Protection)#
# Limit HTTP connections (prevent DoS)
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 80 \
-m limit \
--limit 100/minute \
--limit-burst 50 \
-j ACCEPT
# Limit HTTPS (port 443)
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 443 \
-m limit \
--limit 100/minute \
--limit-burst 50 \
-j ACCEPT
firewall-cmd --reload
Rate Limit Per-Source (Hashlimit)#
# Limit per source IP (good for distributed attacks)
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 22 \
-m hashlimit \
--hashlimit-name SSH-limit \
--hashlimit 5/minute \
--hashlimit-burst 10 \
--hashlimit-htable-size 1024 \
-j ACCEPT
# Drop if over limit
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 1 \
-p tcp \
--dport 22 \
-j DROP
# Check current hashlimit state
cat /proc/net/ipt_hashlimit/SSH-limit 2>/dev/null | head -10
Rate Limit SMTP (Prevent Spam/Abuse)#
# Limit new SMTP connections to prevent spam
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 25 \
-m state \
--state NEW \
-m limit \
--limit 20/minute \
--limit-burst 30 \
-j ACCEPT
# Same for SMTP submissions (465, 587)
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 465 \
-m state \
--state NEW \
-m limit \
--limit 10/minute \
--limit-burst 15 \
-j ACCEPT
firewall-cmd --reload
Connection Tracking (Stateful Firewall)#
# Allow established connections (much faster than checking new ones)
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-m conntrack \
--ctstate ESTABLISHED,RELATED \
-j ACCEPT
# New connections from specific source only
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-s 10.88.0.10 \
-p tcp \
-m state \
--state NEW \
-j ACCEPT
# Drop invalid connections
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-m conntrack \
--ctstate INVALID \
-j DROP
firewall-cmd --reload
๐ฏ RATE LIMITING USE CASES FOR YOUR INFRASTRUCTURE#
Scenario 1: Block VPS Containers from SMTP Spam (With Rate Limit)#
# Allow legitimate SMTP from containers
# But limit to prevent abuse/spam
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-s 10.88.0.0/24 \
-p tcp \
--dport 25 \
-m state \
--state NEW \
-m limit \
--limit 5/minute \
--limit-burst 10 \
-j ACCEPT
# Drop anything over limit
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 1 \
-s 10.88.0.0/24 \
-p tcp \
--dport 25 \
-j DROP
firewall-cmd --reload
Scenario 2: HAProxy Rate Limiting via Interface#
# Rate limit SMTP through HAProxy interface
# Prevents container from saturating connection pool
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-i HAProxy \
-s 10.88.0.10 \
-p tcp \
--dport 465 \
-m conntrack \
--ctstate NEW \
-m limit \
--limit 10/minute \
--limit-burst 5 \
-j ACCEPT
# Drop over limit
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 1 \
-i HAProxy \
-s 10.88.0.10 \
-p tcp \
--dport 465 \
-j DROP
firewall-cmd --reload
Scenario 3: DirectAdmin Login Brute-Force Protection#
# Protect DirectAdmin port 2222 from brute-force
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 2222 \
-m state \
--state NEW \
-m hashlimit \
--hashlimit-name DA-login \
--hashlimit 3/minute \
--hashlimit-burst 5 \
--hashlimit-htable-size 1024 \
--hashlimit-mode srcip \
-j ACCEPT
# Drop anything trying too fast
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 1 \
-p tcp \
--dport 2222 \
-j DROP
firewall-cmd --reload
Scenario 4: HTTP/HTTPS API Rate Limiting#
# Limit API requests to 100 req/min per IP
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 80 \
-m hashlimit \
--hashlimit-name WEB-limit \
--hashlimit 100/minute \
--hashlimit-burst 50 \
--hashlimit-mode srcip \
-j ACCEPT
# Same for HTTPS
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 443 \
-m hashlimit \
--hashlimit-name HTTPS-limit \
--hashlimit 100/minute \
--hashlimit-burst 50 \
--hashlimit-mode srcip \
-j ACCEPT
firewall-cmd --reload
๐ MONITORING RATE LIMITS#
Check Active Limits#
# View current hashlimit state
cat /proc/net/ipt_hashlimit/SSH-limit 2>/dev/null
# Output example:
# 192.168.1.100:22 1/180
# (Source IP limited to 1 packet per 180 seconds)
# Check all hashlimit tables
ls /proc/net/ipt_hashlimit/
# Real-time monitoring of dropped packets
watch -n 1 'iptables -L -n -v | grep DROP'
Log Rate-Limited Packets#
# Enable logging for rate-limited packets
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 22 \
-m state \
--state NEW \
-m hashlimit \
--hashlimit-name SSH-limit \
--hashlimit 5/minute \
-j ACCEPT
# Log drops
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 1 \
-p tcp \
--dport 22 \
-j LOG \
--log-prefix "SSH_RATELIMIT_DROP: " \
--log-level 4
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 2 \
-p tcp \
--dport 22 \
-j DROP
# View logs
journalctl -u firewalld | grep SSH_RATELIMIT_DROP
tail -f /var/log/messages | grep SSH_RATELIMIT_DROP
๐ LOGGING#
Enable Logging#
# Log denied packets
firewall-cmd --permanent --set-log-denied=all
# Options: all | unicast | broadcast | multicast | off
# Check current setting
firewall-cmd --get-log-denied
View Logs#
# Show firewall denials (systemd journal)
journalctl -u firewalld -e
# Show last 50 denials
journalctl -u firewalld -n 50
# Real-time monitoring
journalctl -u firewalld -f
# Search for specific source
journalctl -u firewalld | grep "203.0.113"
# Send to syslog instead
firewall-cmd --permanent --set-log-denied=all
# Check: /var/log/messages for UFW logs (if configured)
๐ USE CASES FOR DirectAdmin/Exim/Dovecot/Virtualizor#
Scenario 1: Secure DirectAdmin Web Interface#
# Allow DirectAdmin port from admin subnet only
firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" port protocol="tcp" port="2222" accept'
# Protect from brute-force: limit to 3 logins/min per IP
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
--dport 2222 \
-m state \
--state NEW \
-m hashlimit \
--hashlimit-name DA-login \
--hashlimit 3/minute \
--hashlimit-burst 5 \
-j ACCEPT
# Drop DirectAdmin from everywhere else
firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family="ipv4" port protocol="tcp" port="2222" reject'
firewall-cmd --reload
Scenario 2: Block SPAM from VPS Network (With Rate Limit)#
# Allow legitimate SMTP from containers but limit to prevent abuse
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-s 10.88.0.0/24 \
-p tcp \
--dport 25 \
-m state \
--state NEW \
-m limit \
--limit 5/minute \
--limit-burst 10 \
-j ACCEPT
# Same for submission ports (465, 587)
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-s 10.88.0.0/24 \
-p tcp \
--dport 465 \
-m state \
--state NEW \
-m limit \
--limit 5/minute \
--limit-burst 10 \
-j ACCEPT
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-s 10.88.0.0/24 \
-p tcp \
--dport 587 \
-m state \
--state NEW \
-m limit \
--limit 5/minute \
--limit-burst 10 \
-j ACCEPT
# Drop anything over limit
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 1 \
-s 10.88.0.0/24 \
-p tcp \
-m multiport \
--dports 25,465,587 \
-j DROP
firewall-cmd --reload
Scenario 3: HAProxy Rate Limiting (Virtualizor NAT)#
# Rate limit SMTP through HAProxy interface to prevent container abuse
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-i HAProxy \
-s 10.88.0.10 \
-p tcp \
--dport 465 \
-m conntrack \
--ctstate NEW \
-m limit \
--limit 10/minute \
--limit-burst 5 \
-j ACCEPT
# Allow established connections (fast path)
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-i HAProxy \
-s 10.88.0.10 \
-p tcp \
--dport 465 \
-m conntrack \
--ctstate ESTABLISHED,RELATED \
-j ACCEPT
# Drop anything over limit
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 1 \
-i HAProxy \
-s 10.88.0.10 \
-p tcp \
--dport 465 \
-j DROP
firewall-cmd --reload
Scenario 4: Allow MariaDB Only From App Servers#
# Allow MySQL 3306 only from application servers
firewall-cmd --permanent --zone=database \
--add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="3306" accept'
# Switch interface to database zone
firewall-cmd --permanent --zone=database --add-interface=eth1
firewall-cmd --reload
Scenario 5: Virtualizor Container Isolation#
# Allow containers to communicate internally
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-s 10.88.0.0/24 \
-d 10.88.0.0/24 \
-j ACCEPT
# Allow containers to reach external through NAT
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-s 10.88.0.0/24 \
-m conntrack \
--ctstate NEW,ESTABLISHED,RELATED \
-j ACCEPT
# Allow return traffic
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 \
-d 10.88.0.0/24 \
-m conntrack \
--ctstate ESTABLISHED,RELATED \
-j ACCEPT
# Enable masquerading for NAT
firewall-cmd --permanent --zone=external --add-masquerade
firewall-cmd --reload
Scenario 6: Whitelist IPs, Block Rest (Default Deny)#
# Set default zone to drop (deny all)
firewall-cmd --set-default-zone=drop
# Whitelist office IP for SSH with rate limiting
firewall-cmd --permanent --zone=drop \
--add-rich-rule='rule family="ipv4" source address="203.0.113.42" service name="ssh" accept'
# Whitelist monitoring server for metrics
firewall-cmd --permanent --zone=drop \
--add-rich-rule='rule family="ipv4" source address="192.168.1.10" port protocol="tcp" port="9100" accept'
# Allow HTTP/HTTPS from anywhere but rate limit
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 \
-p tcp \
-m multiport \
--dports 80,443 \
-m limit \
--limit 100/minute \
-j ACCEPT
# Allow localhost
firewall-cmd --permanent --zone=drop --add-source=127.0.0.1/8
firewall-cmd --reload
๐งน TROUBLESHOOTING#
Check Current Configuration#
# Full dump of all rules
firewall-cmd --list-all
firewall-cmd --list-all-zones
# Check specific zone
firewall-cmd --zone=public --list-all
# Verify permanent vs runtime
firewall-cmd --permanent --zone=public --list-ports
firewall-cmd --zone=public --list-ports
# (If different, changes not reloaded yet)
Test Connectivity#
# From external machine, test if port is open
nc -zv 203.0.113.42 443
# From firewall, check if port is listening
netstat -tlnp | grep :443
ss -tlnp | grep :443
# Check iptables rules directly (bypasses firewall-cmd)
iptables -L -n -v
iptables -t filter -L FORWARD -n -v
Common Issues#
Issue: Rule doesn't apply after adding
# Forgot to reload!
firewall-cmd --reload
Issue: Need to see what changed
# Compare permanent vs runtime
diff <(firewall-cmd --permanent --zone=public --list-all) \
<(firewall-cmd --zone=public --list-all)
Issue: Want to test without permanent
# Add rule to runtime only (no --permanent flag)
firewall-cmd --zone=public --add-port=9999/tcp
# Test connectivity
nc -zv localhost 9999
# If works, make it permanent
firewall-cmd --permanent --zone=public --add-port=9999/tcp
firewall-cmd --reload
# If doesn't work, no need to add --permanent
firewall-cmd --zone=public --remove-port=9999/tcp
โก QUICK REFERENCE - Common Commands#
# Status & Zones
firewall-cmd --state
firewall-cmd --get-default-zone
firewall-cmd --get-active-zones
firewall-cmd --get-zones
# List Rules
firewall-cmd --list-all # Current zone
firewall-cmd --zone=public --list-all # Specific zone
firewall-cmd --list-services # Available services
firewall-cmd --direct --get-all-rules # All direct rules
# Allow (Simple)
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-source=192.168.1.0/24 --zone=trusted
# Allow (Rich Rules - Recommended)
firewall-cmd --permanent --zone=public \
--add-rich-rule='rule family="ipv4" source address="10.88.0.0/24" port protocol="tcp" port="25" reject'
# Allow (Direct - For Advanced/Legacy)
firewall-cmd --permanent --direct \
--add-rule ipv4 filter FORWARD 0 -s 10.88.0.10 -p tcp --dport 465 -j ACCEPT
# Rate Limiting
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 0 -p tcp --dport 22 -m limit --limit 5/minute -j ACCEPT
firewall-cmd --permanent --direct \
--add-rule ipv4 filter INPUT 1 -p tcp --dport 22 -j DROP
# NAT & Forwarding
firewall-cmd --permanent --zone=external --add-masquerade
firewall-cmd --permanent --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=10.88.0.10
# Remove
firewall-cmd --permanent --remove-service=http
firewall-cmd --permanent --remove-port=80/tcp
firewall-cmd --permanent --remove-source=192.168.1.0/24 --zone=trusted
# Apply Changes
firewall-cmd --reload
firewall-cmd --complete-reload # Full reload (disconnect active)
# Logging
journalctl -u firewalld -f # Live logs
firewall-cmd --permanent --set-log-denied=all
firewall-cmd --get-log-denied
# Monitoring
firewall-cmd --direct --get-all-rules
cat /proc/net/ipt_hashlimit/SSH-limit 2>/dev/null
iptables -L -n -v | grep DROP
๐ REFERENCES#
- Man page:
man firewall-cmd - Zone documentation:
man firewalld.zones - Rich rules:
man firewalld.richlanguage - Config dir:
/etc/firewalld/ - Zones dir:
/etc/firewalld/zones/ - Services dir:
/etc/firewalld/services/
๐ฏ BEST PRACTICES#
Rule Management#
- Always use
--permanentflag for changes that should survive reboot - Always reload after adding
--permanentrules:firewall-cmd --reload - Prefer rich rules over direct rules (more readable, maintainable)
- Test in runtime first (without
--permanent), then add--permanent+ reload - Keep rule order consistent (ACCEPT before DROP/REJECT)
Zone Strategy#
- Use zones wisely
public= default, Internet-facing, restrictivetrusted= whitelist all traffic, less restrictivedrop= blacklist all traffic, most restrictiveinternal= internal networks onlyexternal= for NAT/masquerading
- One interface per zone for clarity
- Default to restrictive, allow specific services
Rate Limiting & DoS#
- Always rate limit SSH to prevent brute-force
- SSH: 5-10 connections per minute
- DirectAdmin: 3-5 login attempts per minute
- API: 100+ requests per minute (depends on app)
- Use hashlimit for per-IP limits (better than global limit)
- Set connection burst wisely
- Burst should allow legitimate clients (10-20% of limit)
- But prevent connection floods
- Monitor dropped packets:
watch -n 1 'iptables -L -n -v | grep DROP'
Virtualizor/Container Networking#
- Enable masquerading for container NAT:
--zone=external --add-masquerade - Allow established connections (FAST PATH):
--direct --add-rule ipv4 filter FORWARD 0 \ -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT - Rate limit container output (prevent spam/abuse)
- Separate container traffic from host traffic (different zones)
- Test NAT routing before enabling production
Security Hardening#
- Log denials to detect attacks:
firewall-cmd --permanent --set-log-denied=all - Whitelist then blacklist (default deny is safer than default allow)
- Use connection tracking (conntrack) for stateful rules
- Drop invalid connections:
-m conntrack --ctstate INVALID -j DROP - Review rules monthly:
firewall-cmd --direct --get-all-rules | wc -l
Operational#
- Document why rules exist (add comments in
/etc/firewalld/zones/)# /etc/firewalld/zones/public.xml - Add comments explaining each rule - Backup before major changes
tar czf firewalld-backup-$(date +%Y%m%d).tar.gz /etc/firewalld/ - Version control your firewall rules
git init /etc/firewalld/ git add -A && git commit -m "Initial firewall config" - Test in staging first if possible
- Have a rollback plan - Keep previous rules documented
Monitoring#
- Check status daily:
firewall-cmd --get-active-zones firewall-cmd --list-all iptables -L -n -v | grep -E "Chain|packets" - Alert on rate limit hits:
journalctl -u firewalld | grep -i "ratelimit" - Monitor connection state:
conntrack -L | wc -l # Should be reasonable number cat /proc/net/nf_conntrack_max # Max connections