Firewalld is a dynamic firewall manager for Linux that uses zones to define trust levels for network connections. It sits on top of nftables (or iptables on older systems) and provides a D-Bus interface for runtime and permanent configuration changes – no restart required when you modify rules. The latest release is firewalld 2.4.0 (November 2025), which runs nftables as its default backend on all modern distributions.
This guide covers installing and configuring firewalld on Debian 13 (Trixie) and Ubuntu 24.04 LTS. We walk through zones, services, ports, rich rules, port forwarding, masquerading, and network segmentation. Both distributions ship firewalld in their default repositories, making installation straightforward. For the official project documentation and source code, see the firewalld project site.
Prerequisites
Before starting, confirm you have the following in place:
- A server running Debian 13 or Ubuntu 24.04 LTS
- Root or sudo access
- SSH access (if working remotely – make sure port 22 stays open throughout configuration)
- UFW disabled if previously active (firewalld and UFW conflict)
Step 1: Install Firewalld on Debian 13 / Ubuntu 24.04
Firewalld is available in the default repositories on both Debian and Ubuntu. If you are currently using UFW, disable it first to avoid conflicts.
sudo ufw disable 2>/dev/null; sudo systemctl stop ufw 2>/dev/null
Update the package index and install firewalld:
sudo apt update
sudo apt install firewalld -y
Enable and start the firewalld service so it persists across reboots:
sudo systemctl enable --now firewalld
Verify the service is active and check the installed version:
sudo firewall-cmd --version
sudo systemctl status firewalld --no-pager
The version output shows the firewalld release, and the status should display active (running):
2.1.x
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; preset: enabled)
Active: active (running) since Sat 2026-03-22 10:15:32 UTC; 5s ago
Docs: man:firewalld(1)
Main PID: 1542 (firewalld)
Tasks: 2 (limit: 4652)
Memory: 28.4M
CPU: 512ms
CGroup: /system.slice/firewalld.service
└─1542 /usr/bin/python3 /usr/sbin/firewalld --nofork --nopid
Confirm the firewall backend is nftables (the default on modern systems):
sudo firewall-cmd --get-log-denied
sudo grep FirewallBackend /etc/firewalld/firewalld.conf
You should see FirewallBackend=nftables in the configuration output. This is the recommended backend.
Step 2: Understand Firewalld Zones
Zones are the central concept in firewalld. Each zone defines a trust level for network connections. Network interfaces and source addresses are assigned to zones, and each zone has its own set of allowed services, ports, and rules. Traffic that matches no specific zone falls into the default zone.
List all available zones:
sudo firewall-cmd --get-zones
The output lists every predefined zone:
block dmz drop external home internal nm-shared public trusted work
Check which zone is currently the default:
sudo firewall-cmd --get-default-zone
On a fresh install, the default zone is public. To see all active zones and which interfaces are assigned to them:
sudo firewall-cmd --get-active-zones
Typical output on a single-interface server:
public
interfaces: eth0
To see everything configured in the active zone (services, ports, rules):
sudo firewall-cmd --list-all
This shows the complete configuration for the default zone including allowed services, ports, rich rules, and forwarding settings.
Step 3: Configure the Default Zone
The default zone applies to any interface not explicitly assigned elsewhere. For a public-facing server, public is appropriate. For an internal application server behind a firewall, internal or work may be better suited.
Change the default zone (example – switching to internal):
sudo firewall-cmd --set-default-zone=internal
The change takes effect immediately and persists across reboots. Verify the change:
sudo firewall-cmd --get-default-zone
For most public-facing servers, keep public as the default and add only the services you need. Switch back if you changed it:
sudo firewall-cmd --set-default-zone=public
You can also assign a specific interface to a zone without changing the default:
sudo firewall-cmd --zone=internal --change-interface=eth1 --permanent
sudo firewall-cmd --reload
Step 4: Add Services and Ports in Firewalld
Firewalld uses service definitions to manage common protocols. A service maps a friendly name (like http) to one or more port/protocol combinations. This is the preferred method for allowing traffic since it is self-documenting and easier to audit.
Allow a Service
List all available service definitions:
sudo firewall-cmd --get-services
Allow SSH, HTTP, and HTTPS in the public zone permanently:
sudo firewall-cmd --zone=public --add-service=ssh --permanent
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --reload
The --permanent flag writes the rule to the persistent configuration. The --reload applies permanent rules to the running configuration. Without --permanent, rules are runtime-only and lost on reboot.
Verify the services are active:
sudo firewall-cmd --zone=public --list-services
The output confirms all three services are allowed:
dhcpv6-client http https ssh
Allow a Specific Port
For applications that do not have a predefined service definition, add ports directly. Allow TCP port 8080 and UDP port 5353:
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --zone=public --add-port=5353/udp --permanent
sudo firewall-cmd --reload
Allow a range of ports (useful for services like passive FTP or RTP media):
sudo firewall-cmd --zone=public --add-port=30000-31000/tcp --permanent
sudo firewall-cmd --reload
List all open ports to confirm:
sudo firewall-cmd --zone=public --list-ports
Remove a Service or Port
Remove rules the same way you add them, replacing --add with --remove:
sudo firewall-cmd --zone=public --remove-service=http --permanent
sudo firewall-cmd --zone=public --remove-port=8080/tcp --permanent
sudo firewall-cmd --reload
Step 5: Configure Rich Rules
Rich rules give you fine-grained control that simple service and port rules cannot provide. They support source/destination filtering, logging, rate limiting, and accept/reject/drop actions – all in a single rule.
Allow Access from a Specific IP
Allow SSH only from a management subnet (10.0.1.0/24):
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.1.0/24" service name="ssh" accept' --permanent
sudo firewall-cmd --reload
Block a Specific IP Address
Drop all traffic from a known malicious IP:
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.100" drop' --permanent
sudo firewall-cmd --reload
Rate Limit Connections
Limit SSH connections to 3 per minute per source IP to slow down brute-force attempts. This works well alongside other firewall configurations:
sudo firewall-cmd --zone=public --add-rich-rule='rule service name="ssh" accept limit value="3/m"' --permanent
sudo firewall-cmd --reload
Log Dropped Packets
Log and then drop traffic on port 3306 (MySQL) from external sources. The log prefix makes it easy to filter in syslog:
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" port port="3306" protocol="tcp" log prefix="mysql-blocked:" level="warning" drop' --permanent
sudo firewall-cmd --reload
View all configured rich rules in the current zone:
sudo firewall-cmd --zone=public --list-rich-rules
Step 6: Configure Port Forwarding
Port forwarding redirects incoming traffic on one port to another port or a different server. This is common for exposing internal services through a gateway host.
Forward a Local Port
Forward TCP port 8080 to local port 80 (useful when an application must listen on a non-privileged port but you want users to access it on 8080):
sudo firewall-cmd --zone=public --add-forward-port=port=8080:proto=tcp:toport=80 --permanent
sudo firewall-cmd --reload
Forward to Another Server
Forward incoming traffic on port 443 to an internal web server at 10.0.1.50. This requires masquerading to be enabled (covered in Step 8):
sudo firewall-cmd --zone=public --add-forward-port=port=443:proto=tcp:toport=443:toaddr=10.0.1.50 --permanent
sudo firewall-cmd --reload
IP forwarding must be enabled at the kernel level for cross-host forwarding. Enable it:
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-ip-forward.conf
sudo sysctl -p /etc/sysctl.d/99-ip-forward.conf
Verify that forwarding is active:
sysctl net.ipv4.ip_forward
The output should show net.ipv4.ip_forward = 1.
Step 7: Direct Rules (nftables Passthrough)
Direct rules let you pass raw nftables or iptables commands through firewalld. Use these sparingly – only when the standard firewalld interface cannot express what you need. Direct rules bypass the zone logic, so they are harder to audit.
Block all incoming ICMP echo requests (ping) using a direct rule:
sudo firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p icmp --icmp-type echo-request -j DROP --permanent
sudo firewall-cmd --reload
List all direct rules:
sudo firewall-cmd --direct --get-all-rules
Remove the direct rule when no longer needed:
sudo firewall-cmd --direct --remove-rule ipv4 filter INPUT 0 -p icmp --icmp-type echo-request -j DROP --permanent
sudo firewall-cmd --reload
Note: Direct rules are considered legacy. The firewalld project recommends using rich rules or policies instead. Direct rules may be removed in a future major version.
Step 8: Zone-Based Network Segmentation
On servers with multiple network interfaces, zones provide clean network segmentation. Assign each interface to a zone based on its trust level – public-facing interfaces go to the public or external zone, while internal network interfaces go to internal or trusted.
Assign interfaces to different zones:
sudo firewall-cmd --zone=external --change-interface=eth0 --permanent
sudo firewall-cmd --zone=internal --change-interface=eth1 --permanent
sudo firewall-cmd --reload
Verify the assignment:
sudo firewall-cmd --get-active-zones
The output confirms each interface is in its assigned zone:
external
interfaces: eth0
internal
interfaces: eth1
Now configure each zone independently. Allow only SSH and HTTPS on the external zone:
sudo firewall-cmd --zone=external --add-service=ssh --permanent
sudo firewall-cmd --zone=external --add-service=https --permanent
sudo firewall-cmd --reload
Allow broader access on the internal zone for backend services:
sudo firewall-cmd --zone=internal --add-service=ssh --permanent
sudo firewall-cmd --zone=internal --add-service=mysql --permanent
sudo firewall-cmd --zone=internal --add-service=nfs --permanent
sudo firewall-cmd --zone=internal --add-service=dns --permanent
sudo firewall-cmd --reload
You can also assign source IP ranges to zones. This is useful when you want all traffic from a specific subnet handled by a particular zone, regardless of which interface it arrives on:
sudo firewall-cmd --zone=trusted --add-source=10.0.1.0/24 --permanent
sudo firewall-cmd --reload
Traffic from 10.0.1.0/24 now goes through the trusted zone, which allows all connections by default.
Step 9: Masquerading and NAT
Masquerading (also called source NAT) rewrites the source address of outgoing packets to the gateway’s external IP. This is essential when you are routing traffic from an internal network to the internet through a Linux gateway. If you are configuring a firewall gateway for your network, masquerading is a core requirement.
Enable masquerading on the external zone:
sudo firewall-cmd --zone=external --add-masquerade --permanent
sudo firewall-cmd --reload
Verify masquerading is enabled:
sudo firewall-cmd --zone=external --query-masquerade
The output should return yes. With masquerading active and IP forwarding enabled (from Step 6), internal hosts on eth1 can reach the internet through the gateway.
For a complete NAT gateway setup, make sure IP forwarding is on and both zones are configured:
sysctl net.ipv4.ip_forward
sudo firewall-cmd --zone=external --list-all
sudo firewall-cmd --zone=internal --list-all
This gives you a full picture of what traffic is allowed on each side of the gateway.
Firewalld Zones Reference Table
The following table summarizes all predefined firewalld zones and their default behavior. Choose the zone that best matches the trust level of each network segment:
| Zone | Default Behavior | Typical Use Case |
|---|---|---|
| drop | Drops all incoming, no reply sent | Maximum security – stealth mode |
| block | Rejects all incoming with icmp-host-prohibited | Similar to drop but sends rejection notice |
| public | Allows selected services only (default: ssh, dhcpv6-client) | Internet-facing servers (default zone) |
| external | Like public with masquerading enabled | NAT gateways, external router interfaces |
| dmz | Allows ssh only | DMZ servers with limited access |
| work | Allows ssh, dhcpv6-client | Corporate/office networks |
| home | Allows ssh, mdns, samba-client, dhcpv6-client | Home networks with local services |
| internal | Same defaults as home | Internal/backend networks |
| trusted | Allows all traffic | Fully trusted networks (use with caution) |
Useful Firewalld Management Commands
Here are everyday commands you will use when managing firewalld in production:
Reload firewalld to apply permanent changes without dropping connections:
sudo firewall-cmd --reload
Show the complete configuration of a specific zone:
sudo firewall-cmd --zone=public --list-all
Show configuration across all zones:
sudo firewall-cmd --list-all-zones
Check if a specific service is allowed:
sudo firewall-cmd --zone=public --query-service=https
Switch firewalld to panic mode (blocks ALL traffic – use only in emergencies):
sudo firewall-cmd --panic-on
Disable panic mode to restore normal operation:
sudo firewall-cmd --panic-off
Create a custom service definition for an application that uses a non-standard port. For the cPanel firewalld configuration or any custom app, define the service in XML:
sudo firewall-cmd --permanent --new-service=myapp
sudo firewall-cmd --permanent --service=myapp --set-description="My Custom Application"
sudo firewall-cmd --permanent --service=myapp --add-port=9090/tcp
sudo firewall-cmd --permanent --service=myapp --add-port=9091/tcp
sudo firewall-cmd --reload
Then add the custom service to a zone like any built-in service:
sudo firewall-cmd --zone=public --add-service=myapp --permanent
sudo firewall-cmd --reload
Conclusion
You now have firewalld installed and configured on Debian 13 or Ubuntu 24.04 with zones, services, ports, rich rules, port forwarding, and masquerading. Firewalld replaces the complexity of raw nftables rules with a structured zone-based approach that is straightforward to manage and audit.
For production environments, review your zone assignments regularly, keep the allowed services list minimal, and use rich rules for IP-based access control on sensitive services like SSH and databases. Pair firewalld with SSH hardening (key-only auth, non-standard port) and centralized log monitoring for a strong security posture. Check the firewalld GitHub repository for release notes and updates.