Firewall-CMD Cheatsheet (RHEL/CentOS/AlmaLinux)

Networking #cli#firewall#linux Last updated
Library

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#

  1. Always use --permanent flag for changes that should survive reboot
  2. Always reload after adding --permanent rules: firewall-cmd --reload
  3. Prefer rich rules over direct rules (more readable, maintainable)
  4. Test in runtime first (without --permanent), then add --permanent + reload
  5. Keep rule order consistent (ACCEPT before DROP/REJECT)

Zone Strategy#

  1. Use zones wisely
    • public = default, Internet-facing, restrictive
    • trusted = whitelist all traffic, less restrictive
    • drop = blacklist all traffic, most restrictive
    • internal = internal networks only
    • external = for NAT/masquerading
  2. One interface per zone for clarity
  3. Default to restrictive, allow specific services

Rate Limiting & DoS#

  1. 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)
  2. Use hashlimit for per-IP limits (better than global limit)
  3. Set connection burst wisely
    • Burst should allow legitimate clients (10-20% of limit)
    • But prevent connection floods
  4. Monitor dropped packets: watch -n 1 'iptables -L -n -v | grep DROP'

Virtualizor/Container Networking#

  1. Enable masquerading for container NAT: --zone=external --add-masquerade
  2. Allow established connections (FAST PATH):
    --direct --add-rule ipv4 filter FORWARD 0 \
    -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    
  3. Rate limit container output (prevent spam/abuse)
  4. Separate container traffic from host traffic (different zones)
  5. Test NAT routing before enabling production

Security Hardening#

  1. Log denials to detect attacks: firewall-cmd --permanent --set-log-denied=all
  2. Whitelist then blacklist (default deny is safer than default allow)
  3. Use connection tracking (conntrack) for stateful rules
  4. Drop invalid connections: -m conntrack --ctstate INVALID -j DROP
  5. Review rules monthly: firewall-cmd --direct --get-all-rules | wc -l

Operational#

  1. Document why rules exist (add comments in /etc/firewalld/zones/)
    # /etc/firewalld/zones/public.xml - Add comments explaining each rule
    
  2. Backup before major changes
    tar czf firewalld-backup-$(date +%Y%m%d).tar.gz /etc/firewalld/
    
  3. Version control your firewall rules
    git init /etc/firewalld/
    git add -A && git commit -m "Initial firewall config"
    
  4. Test in staging first if possible
  5. Have a rollback plan - Keep previous rules documented

Monitoring#

  1. Check status daily:
    firewall-cmd --get-active-zones
    firewall-cmd --list-all
    iptables -L -n -v | grep -E "Chain|packets"
    
  2. Alert on rate limit hits:
    journalctl -u firewalld | grep -i "ratelimit"
    
  3. Monitor connection state:
    conntrack -L | wc -l    # Should be reasonable number
    cat /proc/net/nf_conntrack_max  # Max connections
    

Back to library

Keyboard shortcuts

/
Focus search
Esc
Close dialogs, menus and messages
?
Show this help
Ctrl S
Save in the admin editor