Story opener
You just deployed a web server on Fedora. You installed the application, bound it to port 8080, and tried to curl it from another machine. The connection times out. You check the application logs and everything looks fine. The problem is not your code. The firewall is silently dropping the packets before they reach the socket.
What is actually happening
Fedora ships with firewalld running by default. It sits between the kernel netfilter subsystem and your terminal. Instead of writing raw iptables or nftables chains, you manage traffic through a higher-level model built around zones and services. Think of a zone as a trust boundary. Each network interface plugs into exactly one zone at a time. The zone dictates what traffic is allowed in and what gets dropped. A service is just a human-readable label for a group of ports and protocols. When you add the http service, you are not typing port numbers. You are referencing a predefined XML file that maps to port 80 and TCP.
The most important convention to internalize is the split between runtime and persistent configuration. The runtime config lives in memory and applies immediately. The persistent config lives on disk in /etc/firewalld/ and survives a reboot. Any change made with the --permanent flag writes to disk but does not touch the running firewall. You must reload the firewall to merge the disk changes into memory. Forgetting to reload is the number one reason new rules appear to do nothing. The runtime configuration is what the kernel actually enforces. The persistent configuration is just a blueprint. Always treat firewall-cmd --reload as the mandatory second step after every persistent change.
Managing zones and services
Here is how to check your current zone assignment and see what traffic is actually allowed.
# Show the default zone applied to new interfaces
firewall-cmd --get-default-zone
# List every active zone and the interfaces attached to them
firewall-cmd --get-active-zones
# Display the full rule set for the current default zone
firewall-cmd --list-all
Most desktop users stay in the public zone. It accepts established connections and drops everything else. Servers often move to work or internal depending on the network layout. If you need to allow a standard service like SSH or HTTP, you add it to the persistent configuration and reload.
# Write the http service to the persistent configuration on disk
sudo firewall-cmd --permanent --add-service=http
# Merge the persistent configuration into the running firewall
sudo firewall-cmd --reload
The --permanent flag is mandatory for any rule that must survive a reboot. The --reload command applies the changes without dropping existing connections. If you skip --permanent, the rule vanishes the moment you reboot or run --reload. Service definitions live in /usr/lib/firewalld/services/. You should never edit those files directly. Package updates will overwrite them. If you need to modify a service definition, copy it to /etc/firewalld/services/ and edit the copy. The firewall always reads /etc/ first.
Applications that use non-standard ports require explicit port rules. You specify the port number and the protocol.
# Open TCP port 8080 in the persistent configuration
sudo firewall-cmd --permanent --add-port=8080/tcp
# Apply the change to the running firewall immediately
sudo firewall-cmd --reload
Interface assignment is where zone logic becomes practical. If you have a laptop with both Wi-Fi and Ethernet, you might want the Wi-Fi adapter in public and the Ethernet cable in home. You assign interfaces to zones permanently, then reload.
# Bind the enp3s0 interface to the home zone permanently
sudo firewall-cmd --permanent --zone=home --change-interface=enp3s0
# Reload the firewall to apply the zone assignment
sudo firewall-cmd --reload
Predictable network interface names are essential here. Old names like eth0 or wlan0 change across reboots on modern hardware. systemd generates stable names like enp3s0 or wlp2s0. Check your actual names with ip link before writing rules. Hardcoding a volatile name will break your firewall the next time the kernel enumerates the devices.
Fine-grained control with rich rules
When you need granular control beyond standard services and ports, rich rules provide the syntax. Rich rules let you match source IPs, destination ports, and log actions in a single statement. The syntax is strict. Missing a quote or a space will cause the command to fail silently or throw a parse error.
# Allow SSH only from the 192.168.1.0/24 subnet
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="ssh" accept'
# Drop all ICMP traffic from a single host
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.5" protocol value="icmp" drop'
# Reload to activate the new rich rules
sudo firewall-cmd --reload
Rich rules evaluate before zone defaults. This means a rich rule that accepts traffic will override a zone policy that drops it. Use this behavior intentionally. If you want to log dropped packets before they vanish, append log prefix="DROP: " level="info" to the rule. The kernel will write those entries to the journal. You can read them with journalctl -xe. The x flag adds explanatory context and the e flag jumps to the end. Most sysadmins type journalctl -xeu firewalld muscle-memory style when debugging connectivity.
Masquerading turns Fedora into a router. It rewrites the source IP of outgoing packets so internal clients can reach the internet through a single public IP. You enable it on the zone attached to your upstream interface.
# Enable NAT masquerading on the external zone
sudo firewall-cmd --permanent --zone=external --add-masquerade
# Reload to apply routing changes
sudo firewall-cmd --reload
In an emergency where you suspect active exploitation, panic mode drops every packet instantly. It bypasses all zones and rules. Use it only when you need to cut off network access immediately.
# Block all network traffic immediately
sudo firewall-cmd --panic-on
# Restore normal firewall policy
sudo firewall-cmd --panic-off
Panic mode is a blunt instrument. It will kill your SSH session if you are connected remotely. Only trigger it from a local console or a management network that you can physically access.
Verify it worked
Run firewall-cmd --list-all for the zone you modified. The output will show the active services, ports, and rich rules. If you added a port, it appears under ports:. If you added a service, it appears under services:. If the rule is missing, check your persistent configuration with firewall-cmd --permanent --list-all. A mismatch between the two outputs means you forgot to reload. Run firewall-cmd --state to confirm the daemon is actually running. If it returns not running, start the service with sudo systemctl start firewalld. Check the service status before restarting anything. Half the time the symptom is a stopped daemon, not a bad rule.
Common pitfalls and error patterns
The runtime and persistent configurations drift constantly if you mix --permanent and runtime-only commands. Adding a service without --permanent works until you reboot. Adding it with --permanent but forgetting --reload leaves the running firewall unchanged. Always run firewall-cmd --reload after every persistent change. The firewall does not watch the disk for changes. It only updates when you explicitly tell it to.
Rich rules are strict about syntax. If you paste a rule with mismatched quotes, you will see this error:
Error: INVALID_RULE: rule family="ipv4" source address="10.0.0.5" protocol value="icmp" drop
The parser stops at the first syntax violation. Check your quotes and spacing. Rich rules also require the family parameter. Omitting it defaults to IPv4, but explicit is safer. Always wrap the entire rule string in single quotes to prevent the shell from interpreting double quotes or variables.
Zone assignment conflicts happen when NetworkManager changes the interface state. If you manually assign an interface to a zone but NetworkManager has a different profile configured, the next connection event will overwrite your manual change. Let NetworkManager handle zone assignments for dynamic interfaces like Wi-Fi. Use manual firewall-cmd assignments only for static interfaces like bonded Ethernet or bridge ports.
SELinux denials can block firewalld from writing to its configuration directory. If you see permission errors when running --permanent, check the journal with journalctl -t setroubleshoot. The output includes a one-line summary of what was blocked. Fix the context with restorecon -Rv /etc/firewalld/ before disabling SELinux. Disabling the security module to work around a permission error creates a larger problem.
When to use firewalld versus alternatives
Use firewalld when you want a dynamic, zone-based firewall that survives reboots without manual chain management. Use nftables directly when you need low-level packet manipulation or are writing complex routing scripts. Use ufw when you are migrating from Ubuntu and prefer a simpler command syntax. Stick to the default firewalld configuration if you only need to open standard ports and manage basic network boundaries.