Firewalld is the modern dynamic firewall daemon for Linux that replaces iptables as the default firewall management tool. It provides an easier, zone-based approach to firewall configuration with runtime and permanent rule management. This guide covers configuring production server firewalls from a senior developer's perspective.
Why Firewalld
Firewalld offers significant advantages over raw iptables:
- Dynamic Rules: Apply changes without restarting the firewall
- Zone-Based: Group interfaces and sources into security zones
- D-Bus Interface: Programmatic access for applications
- Rich Rules: Human-readable syntax for complex rules
- Service Definitions: Predefined rules for common services
Installation
CentOS/RHEL/Fedora
# Usually preinstalled, but if needed:
sudo dnf install firewalld
# Start and enable.
sudo systemctl start firewalld
sudo systemctl enable firewalld
# Check status.
sudo firewall-cmd --state
Ubuntu/Debian
sudo apt install firewalld
sudo systemctl start firewalld
sudo systemctl enable firewalld
Understanding Zones
Firewalld uses zones to define trust levels for network connections:
| Zone | Description | Default Services |
|---|
| drop | Drop all incoming, no reply | None |
| block | Reject all incoming with message | None |
| public | Untrusted networks (default) | ssh, dhcpv6-client |
| external | For routers with NAT masquerading | ssh |
| dmz | Publicly accessible servers | ssh |
| work | Work environment | ssh, dhcpv6-client |
| home | Home environment | ssh, mdns, dhcpv6-client |
| internal | Internal networks | Same as home |
| trusted | All connections accepted | All |
View Zone Information
# List all zones.
sudo firewall-cmd --get-zones
# Get the default zone.
sudo firewall-cmd --get-default-zone
# Get active zones.
sudo firewall-cmd --get-active-zones
# List everything in a zone.
sudo firewall-cmd --zone=public --list-all
# List all zones with details.
sudo firewall-cmd --list-all-zones
Set Default Zone
# Change the default zone.
sudo firewall-cmd --set-default-zone=home
# Assign an interface to a zone.
sudo firewall-cmd --zone=internal --change-interface=eth1 --permanent
sudo firewall-cmd --reload
Managing Services
List and Enable Services
# List available services.
sudo firewall-cmd --get-services
# List services in a zone.
sudo firewall-cmd --zone=public --list-services
# Add service (runtime only).
sudo firewall-cmd --zone=public --add-service=http
# Add service permanently.
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --reload
# Remove service.
sudo firewall-cmd --zone=public --remove-service=http --permanent
sudo firewall-cmd --reload
Common Services
# Web server.
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
# Database.
sudo firewall-cmd --permanent --add-service=mysql
sudo firewall-cmd --permanent --add-service=postgresql
# Mail.
sudo firewall-cmd --permanent --add-service=smtp
sudo firewall-cmd --permanent --add-service=imap
sudo firewall-cmd --permanent --add-service=imaps
# Apply all.
sudo firewall-cmd --reload
Managing Ports
Open Specific Ports
# Open port (runtime).
sudo firewall-cmd --zone=public --add-port=8080/tcp
# Open port permanently.
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
# Open port range.
sudo firewall-cmd --zone=public --add-port=5000-5100/tcp --permanent
# List open ports.
sudo firewall-cmd --zone=public --list-ports
# Remove port.
sudo firewall-cmd --zone=public --remove-port=8080/tcp --permanent
sudo firewall-cmd --reload
UDP Ports
# DNS.
sudo firewall-cmd --permanent --add-port=53/udp
# VPN.
sudo firewall-cmd --permanent --add-port=1194/udp
# Apply.
sudo firewall-cmd --reload
Rich Rules
Rich rules provide fine-grained control:
Allow from Specific IP
# Allow all from IP.
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.100" accept' --permanent
# Allow a specific port from IP.
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.100" port protocol="tcp" port="3306" accept' --permanent
# Allow from subnet.
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="22" accept' --permanent
Block Specific IP
# Drop all from IP.
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.0.50" drop' --permanent
# Reject (sends response).
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.0.50" reject' --permanent
Rate Limiting
# Limit SSH connections (prevent brute force).
sudo firewall-cmd --zone=public --add-rich-rule='rule service name="ssh" limit value="3/m" accept' --permanent
Logging
# Log dropped packets.
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" log prefix="Dropped: " level="info" drop' --permanent
List Rich Rules
sudo firewall-cmd --zone=public --list-rich-rules
Securing SSH
Best practice: Restrict SSH to trusted IPs only.
# Create a trusted zone for SSH.
sudo firewall-cmd --zone=trusted --add-service=ssh --permanent
# Add trusted IPs.
sudo firewall-cmd --zone=trusted --add-source=192.168.1.0/24 --permanent
sudo firewall-cmd --zone=trusted --add-source=10.20.30.40 --permanent
# Remove SSH from the public zone.
sudo firewall-cmd --zone=public --remove-service=ssh --permanent
# Apply.
sudo firewall-cmd --reload
Port Forwarding
Forward to Another Port
# Forward port 80 to 8080.
sudo firewall-cmd --zone=public --add-forward-port=port=80:proto=tcp:toport=8080 --permanent
# Forward to another IP.
sudo firewall-cmd --zone=public --add-forward-port=port=80:proto=tcp:toaddr=192.168.1.10:toport=8080 --permanent
# Enable masquerading (required for forwarding to other hosts).
sudo firewall-cmd --zone=public --add-masquerade --permanent
sudo firewall-cmd --reload
Custom Service Definitions
Create Custom Service
# Create service file.
sudo cat > /etc/firewalld/services/myapp.xml << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>MyApp</short>
<description>My Custom Application</description>
<port protocol="tcp" port="3000"/>
<port protocol="tcp" port="3001"/>
</service>
EOF
# Reload to recognize the new service.
sudo firewall-cmd --reload
# Use the service.
sudo firewall-cmd --zone=public --add-service=myapp --permanent
sudo firewall-cmd --reload
Production Server Example
Complete setup for a typical web server:
##!/bin/bash
# Secure web server firewall configuration.
# Set the default zone.
firewall-cmd --set-default-zone=public
# Remove unnecessary services.
firewall-cmd --zone=public --remove-service=dhcpv6-client --permanent
# Web services.
firewall-cmd --zone=public --add-service=http --permanent
firewall-cmd --zone=public --add-service=https --permanent
# SSH from trusted IPs only.
firewall-cmd --zone=trusted --add-service=ssh --permanent
firewall-cmd --zone=trusted --add-source=10.0.0.0/8 --permanent
firewall-cmd --zone=public --remove-service=ssh --permanent
# Application ports (internal only).
firewall-cmd --zone=internal --add-port=3000/tcp --permanent # Node.js
firewall-cmd --zone=internal --add-port=6379/tcp --permanent # Redis
firewall-cmd --zone=internal --add-source=10.0.1.0/24 --permanent
# Rate-limit public web traffic.
firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" service name="http" limit value="25/s" accept' --permanent
# Log dropped packets (for debugging).
firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" log prefix="DROPPED: " level="warning" limit value="5/m"' --permanent
# Apply all changes.
firewall-cmd --reload
# Verify.
firewall-cmd --list-all-zones
Runtime vs Permanent
# Runtime only (lost after restart).
sudo firewall-cmd --add-service=http
# Permanent (persists after restart).
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --reload
# Make runtime rules permanent.
sudo firewall-cmd --runtime-to-permanent
# Reload permanent rules (discard runtime changes).
sudo firewall-cmd --reload
# Complete restart.
sudo firewall-cmd --complete-reload
Reset to Defaults
# Remove custom zone files.
sudo rm /etc/firewalld/zones/*
# Reload defaults.
sudo firewall-cmd --complete-reload
Troubleshooting
Check Logs
# View firewalld logs.
sudo journalctl -u firewalld
# View dropped packets (if logging enabled).
sudo journalctl -k | grep DROPPED
Test Connectivity
# From another machine.
nc -zv server-ip 80
nmap -p 22,80,443 server-ip
Key Takeaways
- Use zones: Match network trust levels to zones
- Permanent + reload: Always use
--permanent and --reload - Restrict SSH: Move SSH to trusted zone with specific sources
- Rich rules for precision: IP-based filtering and rate limiting
- Service definitions: Create reusable service files for applications
- Log carefully: Enable logging for dropped packets during debugging
Firewalld provides a robust, manageable firewall that balances security with usability—essential for any production Linux server.