Firewalld is a dynamic firewall manager for Linux that supports IPv4, IPv6, Ethernet bridges, and IPSet configurations. It acts as a front-end to the kernel’s netfilter framework, using nftables as its default backend on modern systems. While Ubuntu ships with UFW as its default firewall, firewalld offers zone-based management, rich rules, and runtime-vs-permanent configuration that many administrators prefer – especially those managing mixed RHEL/Ubuntu environments.
This guide walks through installing, configuring, and mastering firewalld on Ubuntu 24.04 and 22.04. We cover zones, services, rich rules, NAT/masquerading, IP sets, Docker integration, and troubleshooting common issues.
Firewalld vs UFW – When to Use Which
Ubuntu defaults to UFW (Uncomplicated Firewall), which is a simpler front-end to iptables/nftables. Here is how the two compare:
| Feature | UFW | Firewalld |
|---|---|---|
| Default on | Ubuntu / Debian | RHEL / Fedora / SUSE |
| Zone support | No | Yes – multiple zones per interface |
| Rich rules | No (requires manual iptables) | Yes – built-in rich rule language |
| Runtime vs permanent | All changes are permanent | Separate runtime and permanent configs |
| IP sets | No native support | Built-in ipset integration |
| D-Bus interface | No | Yes – programmatic control |
| Learning curve | Low | Moderate |
| Best for | Single-interface servers, simple rules | Multi-NIC servers, zone segmentation, complex policies |
Use firewalld when you manage multi-NIC servers, need zone-based segmentation, run mixed RHEL/Ubuntu environments, or need rich rules and IP sets. Stick with UFW for simple single-interface servers where basic allow/deny rules are sufficient.
Step 1 – Install Firewalld on Ubuntu
Firewalld is available in the default Ubuntu repositories. Install it with apt.
sudo apt update
sudo apt install firewalld -y
If UFW is currently active, disable it first to avoid conflicts between the two firewalls.
sudo ufw disable
Enable and start the firewalld service.
sudo systemctl enable --now firewalld
Verify the service is running.
sudo firewall-cmd --state
Expected output:
running
Check the installed version.
sudo firewall-cmd --version
Step 2 – Understanding Firewalld Zones
Zones are the core concept in firewalld. Each zone defines a trust level for network connections and interfaces assigned to it. Traffic entering through an interface is handled by the rules of its assigned zone.
Reference Table of Zones
Firewalld ships with these predefined zones, ordered from least to most trusted.
| Zone | Default Behavior | Use Case |
|---|---|---|
| drop | Drop all incoming, no reply. Outgoing allowed. | Stealth mode, honeypots |
| block | Reject all incoming with icmp-host-prohibited. Outgoing allowed. | Untrusted networks with rejection notice |
| public | Only selected services allowed (ssh, dhcpv6-client by default) | Public-facing servers (default zone) |
| external | Masquerading enabled, only ssh allowed | NAT routers, external-facing interface |
| dmz | Only ssh allowed | DMZ servers with limited access |
| work | ssh, dhcpv6-client allowed | Work networks |
| home | ssh, mdns, samba-client, dhcpv6-client allowed | Home networks |
| internal | Same as home | Internal/LAN-facing interfaces |
| trusted | Accept all traffic | Fully trusted networks (use with caution) |
List all available zones.
sudo firewall-cmd --get-zones
Check your current default zone.
sudo firewall-cmd --get-default-zone
List all active zones and their assigned interfaces.
sudo firewall-cmd --get-active-zones
View the full configuration of the default zone.
sudo firewall-cmd --list-all
Step 3 – Managing Services and Ports
Firewalld uses predefined service definitions stored in /usr/lib/firewalld/services/. These map service names to their port/protocol combinations. Custom service files go in /etc/firewalld/services/.
List Available Services
See all services that firewalld knows about.
sudo firewall-cmd --get-services
Add a Service
Allow HTTP traffic permanently.
sudo firewall-cmd --add-service=http --permanent
Allow both HTTP and HTTPS in a single command.
sudo firewall-cmd --add-service={http,https} --permanent
After any --permanent change, reload firewalld to apply it to the runtime configuration.
sudo firewall-cmd --reload
Add a Port by Number
Open a specific TCP port.
sudo firewall-cmd --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
Open a UDP port.
sudo firewall-cmd --add-port=514/udp --permanent
sudo firewall-cmd --reload
Open a range of ports.
sudo firewall-cmd --add-port=5000-5100/tcp --permanent
sudo firewall-cmd --reload
Remove a Service or Port
To remove a rule, replace --add with --remove.
sudo firewall-cmd --remove-service=http --permanent
sudo firewall-cmd --remove-port=8080/tcp --permanent
sudo firewall-cmd --reload
Verify Current Rules
List all services and ports currently allowed in the default zone.
sudo firewall-cmd --list-services
sudo firewall-cmd --list-ports
Step 4 – Zone-Based Network Segmentation
On multi-NIC servers, you can assign each interface to a different zone. This is useful for separating public traffic from internal management traffic or database connections.
Consider a server with three interfaces: eth0 (public), eth1 (internal app network), and eth2 (management). Assign each to its appropriate zone.
sudo firewall-cmd --zone=public --change-interface=eth0 --permanent
sudo firewall-cmd --zone=internal --change-interface=eth1 --permanent
sudo firewall-cmd --zone=trusted --change-interface=eth2 --permanent
Configure the public zone to allow only web traffic.
sudo firewall-cmd --zone=public --add-service={http,https} --permanent
Allow application-specific ports on the internal zone.
sudo firewall-cmd --zone=internal --add-port=5432/tcp --permanent
sudo firewall-cmd --zone=internal --add-port=6379/tcp --permanent
The trusted zone on eth2 allows all traffic, so no additional rules are needed there. Reload to apply all changes.
sudo firewall-cmd --reload
Verify the zone assignments.
sudo firewall-cmd --get-active-zones
Create a Custom Zone
You can create your own zones for specific use cases.
sudo firewall-cmd --permanent --new-zone=appservers
sudo firewall-cmd --reload
sudo firewall-cmd --zone=appservers --add-port=8080/tcp --permanent
sudo firewall-cmd --zone=appservers --add-port=8443/tcp --permanent
sudo firewall-cmd --reload
Set the default zone for interfaces not explicitly assigned.
sudo firewall-cmd --set-default-zone=public
Step 5 – Rich Rules
Rich rules provide fine-grained control over traffic matching. They support source/destination filtering, logging, rate limiting, and more – all in a readable syntax without touching raw iptables.
Allow a Service from a Specific IP or Subnet
Allow SSH access from a single IP address.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100/32" service name="ssh" accept'
Allow SSH from an entire subnet.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/24" service name="ssh" accept'
Rate Limit SSH Connections
Limit incoming SSH connections to 3 per minute per source IP. This helps mitigate brute-force attacks without completely blocking legitimate users who mistype passwords.
sudo firewall-cmd --permanent --add-rich-rule='rule service name="ssh" accept limit value="3/m"'
sudo firewall-cmd --reload
Log and Drop Traffic from a Source
Log and drop all traffic from a known bad IP. The log prefix makes it easy to filter in syslog or journalctl.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.50" log prefix="BLOCKED: " level="warning" drop'
sudo firewall-cmd --reload
Check logs for blocked traffic.
sudo journalctl -k | grep "BLOCKED:"
Port Forwarding with Rich Rules
Forward incoming traffic on port 80 to an internal server on port 8080.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" forward-port port="80" protocol="tcp" to-port="8080" to-addr="10.0.0.5"'
sudo firewall-cmd --reload
Reject a Specific Port with ICMP Message
Reject connections on port 23 (telnet) with a clear rejection message instead of silently dropping.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="23" protocol="tcp" reject type="icmp-port-unreachable"'
sudo firewall-cmd --reload
List and Remove Rich Rules
View all rich rules in the current zone.
sudo firewall-cmd --list-rich-rules
Remove a specific rich rule by passing the exact rule string with --remove-rich-rule.
sudo firewall-cmd --permanent --remove-rich-rule='rule service name="ssh" accept limit value="3/m"'
sudo firewall-cmd --reload
Step 6 – Direct Rules for Advanced iptables-Like Control
Direct rules give you raw access to the iptables/nftables chains through firewalld. Use these when you need something that rich rules cannot express – custom chains, specific ICMP handling, or complex matching. Direct rules are added to the runtime and optionally made permanent.
Block all incoming ICMP echo requests (ping).
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p icmp --icmp-type echo-request -j DROP
sudo firewall-cmd --reload
Log new incoming connections on port 443 for debugging.
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 443 -m state --state NEW -j LOG --log-prefix "HTTPS-NEW: "
sudo firewall-cmd --reload
List all direct rules.
sudo firewall-cmd --direct --get-all-rules
Remove a direct rule.
sudo firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -p icmp --icmp-type echo-request -j DROP
sudo firewall-cmd --reload
Note: Direct rules are considered legacy in firewalld 1.0+. Prefer rich rules and policies where possible. Direct rules do not integrate with zones and are processed before zone rules.
Step 7 – Masquerading and NAT (Gateway/Router Configuration)
Masquerading (NAT) allows a server to act as a gateway, forwarding traffic from an internal network to the internet. This is common on servers with one public and one private interface.
First, enable IP forwarding in the kernel.
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
Enable masquerading on the public-facing zone.
sudo firewall-cmd --zone=public --add-masquerade --permanent
sudo firewall-cmd --reload
Verify masquerading is enabled.
sudo firewall-cmd --zone=public --query-masquerade
Expected output:
yes
Port Forwarding Through the Gateway
Forward incoming traffic on port 80 of the gateway to an internal web server at 10.0.0.5.
sudo firewall-cmd --zone=public --add-forward-port=port=80:proto=tcp:toport=80:toaddr=10.0.0.5 --permanent
sudo firewall-cmd --reload
Forward port 2222 on the gateway to SSH (port 22) on an internal server.
sudo firewall-cmd --zone=public --add-forward-port=port=2222:proto=tcp:toport=22:toaddr=10.0.0.10 --permanent
sudo firewall-cmd --reload
Step 8 – IP Sets for Whitelist and Blacklist Management
IP sets let you manage groups of IP addresses as a single entity. This is far more efficient than creating individual rich rules for dozens or hundreds of IPs.
Create a Blacklist IP Set
Create an IP set for blocked addresses.
sudo firewall-cmd --permanent --new-ipset=blacklist --type=hash:ip
sudo firewall-cmd --reload
Add IPs to the blacklist.
sudo firewall-cmd --permanent --ipset=blacklist --add-entry=203.0.113.50
sudo firewall-cmd --permanent --ipset=blacklist --add-entry=198.51.100.23
sudo firewall-cmd --reload
Create a rich rule that drops all traffic from the blacklist.
sudo firewall-cmd --permanent --add-rich-rule='rule source ipset="blacklist" drop'
sudo firewall-cmd --reload
Create a Whitelist IP Set
Similarly, create a whitelist for trusted management IPs and allow SSH only from those addresses.
sudo firewall-cmd --permanent --new-ipset=management --type=hash:net
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --ipset=management --add-entry=10.0.0.0/24
sudo firewall-cmd --permanent --ipset=management --add-entry=192.168.1.0/24
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --add-rich-rule='rule source ipset="management" service name="ssh" accept'
sudo firewall-cmd --reload
Manage IP Sets
List all IP sets.
sudo firewall-cmd --get-ipsets
List entries in a specific IP set.
sudo firewall-cmd --ipset=blacklist --get-entries
Remove an entry from an IP set.
sudo firewall-cmd --permanent --ipset=blacklist --remove-entry=203.0.113.50
sudo firewall-cmd --reload
Step 9 – Firewalld with Docker
Docker manipulates iptables directly to expose container ports, which bypasses firewalld entirely. This means containers with published ports are accessible from any source, even if your firewalld rules say otherwise. This is a common security surprise on Ubuntu servers running both Docker and firewalld.
Option 1 – Disable Docker’s iptables Management
Tell Docker not to manipulate iptables. This gives firewalld full control but means you must manually create rules for container networking.
Create or edit the Docker daemon configuration.
sudo tee /etc/docker/daemon.json > /dev/null <<EOF
{
"iptables": false
}
EOF
Restart Docker.
sudo systemctl restart docker
Now you need to allow container traffic through firewalld manually. For a container exposed on port 8080, add the port to the public zone.
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
If containers need to talk to each other or reach the internet, allow masquerading and trust the Docker bridge.
sudo firewall-cmd --zone=trusted --add-interface=docker0 --permanent
sudo firewall-cmd --zone=public --add-masquerade --permanent
sudo firewall-cmd --reload
Option 2 – Use a Dedicated Docker Zone
A more controlled approach is to create a dedicated zone for Docker traffic while leaving Docker’s iptables management enabled.
sudo firewall-cmd --permanent --new-zone=docker
sudo firewall-cmd --reload
sudo firewall-cmd --zone=docker --add-interface=docker0 --permanent
sudo firewall-cmd --zone=docker --add-masquerade --permanent
sudo firewall-cmd --reload
This keeps Docker traffic isolated in its own zone where you can apply specific policies without affecting your main public zone rules.
Important: After changing Docker’s iptables settings, test that your containers can still reach external services and that inter-container networking works as expected.
Step 10 – Runtime vs Permanent Configuration
Firewalld maintains two separate configurations: runtime (active, lost on restart) and permanent (saved, loaded on restart). This is a key difference from UFW where all changes are permanent.
Add a rule to runtime only (for testing) – omit --permanent.
sudo firewall-cmd --add-port=9090/tcp
This rule takes effect immediately but is lost when firewalld restarts. If the rule works correctly, make it permanent.
sudo firewall-cmd --runtime-to-permanent
Alternatively, save all current runtime rules as the permanent configuration.
sudo firewall-cmd --runtime-to-permanent
To discard runtime changes and revert to the permanent configuration.
sudo firewall-cmd --reload
Troubleshooting Common Firewalld Issues on Ubuntu
Here are problems you will likely encounter when running firewalld on Ubuntu, along with their fixes.
Conflict Between UFW and Firewalld
Running both UFW and firewalld simultaneously causes unpredictable behavior. If services are unreachable after enabling firewalld, check if UFW is still active.
sudo ufw status
If UFW shows active, disable it.
sudo ufw disable
sudo systemctl disable ufw
sudo systemctl mask ufw
Masking the service prevents it from being accidentally re-enabled.
Services Not Reachable After Enabling Firewalld
If your services stop working after enabling firewalld, it is because the default public zone only allows SSH and DHCPv6. Check what is currently allowed.
sudo firewall-cmd --list-all
Add back the services you need.
sudo firewall-cmd --add-service={http,https,dns} --permanent
sudo firewall-cmd --reload
Permanent Rules Not Taking Effect
If you added rules with --permanent but they are not active, you forgot to reload.
sudo firewall-cmd --reload
Firewalld Fails to Start
Check the journal for error details.
sudo journalctl -u firewalld --no-pager -n 50
A common cause is a malformed XML file in /etc/firewalld/. Validate zone files.
sudo firewall-cmd --check-config
Docker Containers Bypassing Firewalld
If Docker containers are accessible from the internet despite your firewalld rules blocking those ports, Docker is inserting its own iptables rules. Refer to Step 9 above for the fix.
Check Which Backend Firewalld Uses
On Ubuntu 24.04, firewalld uses nftables by default. Verify the backend.
grep FirewallBackend /etc/firewalld/firewalld.conf
Expected output:
FirewallBackend=nftables
Useful Firewalld Command Reference
Here is a quick reference of the most commonly used firewalld commands.
| Command | Purpose |
|---|---|
firewall-cmd --state | Check if firewalld is running |
firewall-cmd --reload | Reload permanent rules into runtime |
firewall-cmd --list-all | Show all rules for the default zone |
firewall-cmd --list-all-zones | Show rules for every zone |
firewall-cmd --get-active-zones | Show zones with assigned interfaces |
firewall-cmd --get-default-zone | Show the default zone name |
firewall-cmd --add-service=NAME --permanent | Allow a service permanently |
firewall-cmd --add-port=PORT/PROTO --permanent | Open a port permanently |
firewall-cmd --runtime-to-permanent | Save runtime config as permanent |
firewall-cmd --panic-on | Block all traffic immediately (emergency) |
firewall-cmd --panic-off | Disable panic mode |
For further reading, refer to the official Firewalld documentation. If you are configuring firewalld on RHEL-based systems, see our guide on configuring firewalld on Rocky Linux / AlmaLinux / RHEL. For the simpler UFW alternative, check UFW firewall usage commands with examples.