You changed a port and now nothing connects
You just deployed a web application or a database on your Fedora machine. You try to reach it from another device on the same network and the connection times out. The service is running, the application logs show it is listening, but the traffic never arrives. The firewall is doing exactly what it was designed to do. You need to tell firewalld which traffic to accept without leaving the entire system exposed to the internet.
How firewalld actually routes traffic
firewalld sits between your network interfaces and the kernel packet filtering layer. It does not maintain a single flat list of allow and deny rules. It uses a zone-based model. Each network interface gets assigned to one zone, and the rules defined for that zone apply to all traffic hitting that interface. Think of zones like security checkpoints. The public zone treats every incoming connection as untrusted and only allows essential services like SSH and DHCP. The home zone assumes you are on a private network and automatically allows file sharing, printer discovery, and mDNS. The trusted zone accepts everything. Fedora ships with sensible defaults, but you must align your interfaces with the correct zones for your environment.
The daemon maintains two separate configuration states. The runtime configuration lives in memory and takes effect immediately. The permanent configuration lives on disk and survives reboots. This separation prevents accidental lockouts when you are managing the system over SSH. If you add a rule to the permanent configuration, it does not apply until you explicitly reload the daemon. If you add a rule to the runtime configuration, it vanishes on the next reboot. Understanding this split is the foundation of safe firewall management.
Check the current state and see which interfaces are attached to which zones.
sudo firewall-cmd --state
# Confirms the daemon is running and ready to accept commands
sudo firewall-cmd --get-active-zones
# Lists every active zone and the interfaces assigned to it
Reboot before you debug. Half the time the symptom is gone.
Add rules without locking yourself out
You will almost always want to add rules by service name rather than raw port numbers. Fedora ships with predefined service definition files that map human-readable names to the correct port, protocol, and sometimes D-Bus or multicast addresses. This approach keeps your configuration readable and automatically updates when upstream services change their default ports.
Add a predefined service to the permanent configuration and apply it immediately.
sudo firewall-cmd --permanent --add-service=ssh
# Writes the rule to disk so it survives reboots
sudo firewall-cmd --reload
# Applies the permanent configuration to the running daemon without dropping active connections
If you are running a custom application that does not have a predefined service definition, you can open a raw port. Specify the protocol explicitly to avoid accidentally opening both TCP and UDP.
sudo firewall-cmd --permanent --add-port=8080/tcp
# Opens port 8080 for TCP traffic in the permanent configuration
sudo firewall-cmd --reload
# Pushes the change to the runtime configuration
Assigning interfaces to the correct zone is where most misconfigurations happen. If your laptop connects to both a trusted home network and a public coffee shop, you want different rules for each connection.
sudo firewall-cmd --permanent --zone=home --add-interface=enp3s0
# Attaches the specified network interface to the home zone permanently
sudo firewall-cmd --reload
# Activates the zone assignment immediately
For granular control, rich rules let you combine source addresses, destination ports, and protocols in a single statement. Use them when you need to restrict access to a specific subnet or IP range.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="http" accept'
# Allows HTTP traffic only from the specified local subnet
sudo firewall-cmd --reload
# Applies the rich rule to the active firewall
Convention aside: always run firewall-cmd --reload after every permanent change. The runtime configuration and the persistent configuration will diverge if you skip this step. Divergence causes rules to disappear after a reboot or fail to apply when you expect them to.
Run firewall-cmd --reload after every change. Trust the package manager. Manual file edits drift, snapshots stay.
Verify the configuration
Never assume a rule is active because the command returned a success message. Query the daemon directly to confirm the rule exists in both the runtime and permanent configurations.
sudo firewall-cmd --zone=public --list-all
# Shows every service, port, rich rule, and interface attached to the public zone
sudo firewall-cmd --permanent --zone=public --list-all
# Shows what will be loaded after the next reboot
If you need to check whether a specific service or port is allowed, use the query commands. They return a simple yes or no instead of dumping the entire zone configuration.
sudo firewall-cmd --query-service=ssh
# Returns yes if SSH is allowed in the default zone, otherwise returns nothing
sudo firewall-cmd --query-port=8080/tcp
# Returns yes if the specified port and protocol are open
Check the actual output before moving on. Guessing about firewall state wastes hours.
Common pitfalls and exact error messages
The most frequent mistake is mixing runtime and permanent commands without understanding the separation. If you add a rule without --permanent, it works until you reload or reboot. If you add a rule with --permanent but forget to reload, it sits on disk until the next boot. Both scenarios look like the rule failed.
Another trap is editing configuration files directly. firewalld stores default service definitions in /usr/lib/firewalld/services/. User modifications belong in /etc/firewalld/services/. The daemon merges both directories, but files in /etc/ always override files in /usr/lib/. Edit /etc/. Never edit /usr/lib/. Package updates will overwrite your changes and break your firewall.
Rich rules are strict about syntax. A missing quote or a misplaced space will cause the daemon to reject the entire command. You will see this exact error when the parser fails:
Error: COMMAND_FAILED: '/usr/sbin/iptables -w2 -D INPUT -p tcp -m tcp --dport 8080 -j ACCEPT' failed: iptables: No chain/target/match by that name.
The error message references iptables because firewalld uses iptables or nftables as its backend. The actual problem is usually a malformed rich rule or a zone that does not exist. Check your quoting and verify the zone name with --get-zones.
If the daemon itself fails to start, check the journal for the specific unit failure.
journalctl -xeu firewalld
# Shows recent log lines and explanatory text for the firewalld service
Read the actual error before guessing. SELinux denials and missing backend binaries show up here long before you notice dropped packets.
Run journalctl -xeu firewalld first. Read the actual error before guessing.
Choose the right tool for your workflow
Use firewalld when you want a dynamic, zone-based firewall that survives reboots without restarting the daemon. Use iptables directly when you need low-level packet manipulation that firewalld does not expose. Use the GNOME Firewall GUI when you prefer a visual interface for managing zones and services. Use ufw when you are migrating from Ubuntu and want a simpler, flat-rule interface. Use nftables directly when you are writing production-grade firewall scripts that require advanced set operations and performance tuning.
Trust the package manager. Manual file edits drift, snapshots stay.