How to Configure firewalld Using firewall-cmd on Fedora

firewall-cmd is the primary command-line tool for managing firewalld on Fedora, letting you add services, open ports, and manage zones with permanent or runtime rules.

You opened a port and the connection still drops

You just deployed a web service on port 3000. The application is running. curl localhost:3000 returns the expected JSON. You try to hit it from another machine on the same network and get a timeout. You check the application logs. Nothing. You check the systemd service status. Active and running. The problem is not your application. The problem is the firewall sitting between your network interface and the process. Fedora ships with firewalld enabled by default, and it blocks everything except explicitly allowed traffic.

How firewalld actually manages traffic

firewalld is a dynamic firewall manager. It sits on top of nftables and translates your human-readable rules into kernel-level packet filters. Think of it like a building security desk. The desk keeps a list of approved visitors. If a packet arrives and matches an approved service or port, the desk waves it through. If it does not match, the desk drops it silently. The system does not send a rejection packet back to the sender. That is why you see a timeout instead of a connection refused error.

The daemon maintains two separate rule sets. The runtime configuration lives in memory and controls what the kernel enforces right now. The permanent configuration lives on disk and survives reboots. You can modify either one independently. Modifying the runtime set takes effect immediately but disappears when the system restarts. Modifying the permanent set writes to disk but does not change live traffic until you explicitly reload the daemon. This split exists so you can test changes without locking yourself out of a remote server.

Run systemctl status firewalld first. Read the actual state before guessing.

Check what is currently allowed

Before changing rules, verify the daemon is running and see what it is currently enforcing. You need to know which zone your interface is using and what services are already open.

sudo firewall-cmd --state
# Returns running or not-running. Confirms the daemon is active.
sudo firewall-cmd --get-active-zones
# Lists zones and the interfaces assigned to them.
sudo firewall-cmd --list-all
# Shows the full runtime configuration for the default zone.

The --list-all output shows the zone name, target, interfaces, sources, services, ports, masquerade status, forward ports, icmp-blocks, rich rules, and ipsets. This is the live configuration. Changes you make without the --permanent flag only live in this runtime view. They vanish on reboot.

Check the active zone first. Half the time the rule is correct but applied to the wrong zone.

Add a service or open a port

Fedora maintains a catalog of named services. Each service maps to a specific port, protocol, and sometimes helper programs. Using the service name is safer than hardcoding a port number because the package manager updates the port mapping automatically if the upstream standard changes.

sudo firewall-cmd --permanent --add-service=http
# Writes the http service rule to the persistent configuration file.
sudo firewall-cmd --permanent --add-service=https
# Adds https to the permanent rule set alongside http.
sudo firewall-cmd --reload
# Flushes the runtime table and loads the permanent rules from disk.

Always run firewall-cmd --reload after adding or removing permanent rules. The runtime configuration and the persistent configuration diverge until you reload. Skipping the reload is the most common reason users think their rules did not save.

If you need to open a custom port that does not have a service definition, specify the port number and protocol explicitly.

sudo firewall-cmd --permanent --add-port=3000/tcp
# Opens TCP port 3000 in the permanent configuration.
sudo firewall-cmd --reload
# Applies the new port rule to the live kernel filter.
sudo firewall-cmd --list-ports
# Verifies the port appears in the active runtime table.

Configuration files live in /etc/firewalld/. You can also use the firewall-config GUI for a visual interface, but the command line gives you exact control and leaves an audit trail in your shell history.

Trust the package manager. Manual file edits drift, snapshots stay.

Scope rules to specific networks with zones

Interfaces are assigned to zones. Zones define trust levels for network traffic. The public zone is the default for most desktop and server setups. It allows only essential services like SSH and DHCP. The home zone trusts other computers on the network and opens services like Samba and mDNS. The drop zone silently discards all incoming packets. You can assign multiple interfaces to different zones simultaneously.

sudo firewall-cmd --permanent --zone=home --add-interface=wlan0
# Binds the wireless interface to the home zone permanently.
sudo firewall-cmd --permanent --zone=home --add-port=5900/tcp
# Opens VNC traffic only in the home zone.
sudo firewall-cmd --reload
# Activates the zone assignment and port rule.

Check your active zones with --get-active-zones. If an interface is not listed, it is using the default zone. You can change the default zone in /etc/firewalld/firewalld.conf, but assigning interfaces explicitly is cleaner and survives network manager profile changes.

Run --get-active-zones before you add rules. Targeting the wrong zone wastes time.

Fine-tune access with rich rules

Sometimes a simple service or port rule is too broad. You need to allow SSH only from your office subnet, or block a specific IP address entirely. Rich rules let you add source filtering, port forwarding, and logging without writing raw nftables syntax.

sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=10.0.0.0/8 service name=ssh accept'
# Allows SSH only from the 10.0.0.0/8 subnet.
sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=203.0.113.5 drop'
# Silently drops all traffic from a single hostile IP.
sudo firewall-cmd --reload
# Loads the rich rules into the active filter chain.

Rich rules use a specific XML-derived syntax. Keep the entire rule in single quotes to prevent the shell from interpreting spaces and special characters. You can also add log prefix="SSH-Attempt" level=info before the accept or drop action to write matching packets to the journal.

Verify rich rules with sudo firewall-cmd --list-rich-rules. The output shows exactly how the daemon parsed your syntax.

Reload after every rule change. Otherwise the runtime config and the persistent config diverge.

Emergency lockdown and recovery

If you suspect a breach or need to cut off all network access immediately, panic mode drops every packet except those initiated from the local machine. It overrides all other rules and zones.

sudo firewall-cmd --panic-on
# Immediately drops all incoming and outgoing traffic except established connections.
sudo firewall-cmd --panic-off
# Restores normal firewall operation and reloads the active zone rules.

Panic mode is useful for emergency lockdowns, but it will also drop your SSH session if you are not careful. Use it from a local console or a trusted out-of-band management interface. The state does not persist across reboots, so you do not need to worry about leaving the system locked down after a crash.

Reboot before you debug. Half the time the symptom is gone.

Common pitfalls and error messages

Users frequently run into three specific problems. The first is forgetting the --permanent flag. The rule works until you reboot, then vanishes. The second is forgetting --reload. The rule sits on disk but never reaches the kernel. The third is targeting the wrong zone. The rule exists but applies to an interface that is not currently active.

If you try to add a service that does not exist, firewall-cmd prints Error: INVALID_SERVICE. Check available services with sudo firewall-cmd --get-services. The list is long. Pipe it to grep if you are searching for something specific.

If you see Error: COMMAND_FAILED, the underlying nftables backend failed to apply the rule. Check journalctl -xeu firewalld for the actual kernel rejection reason. SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux.

Run journalctl -xe first. Read the actual error before guessing.

When to use which approach

Use named services when you are opening standard ports like HTTP, SSH, or DNS. Use explicit port numbers when you are running custom applications that do not have a firewalld service definition. Use zones when you need different trust levels for different network interfaces. Use rich rules when you need source IP filtering, port forwarding, or logging. Use panic mode only when you need an immediate, system-wide network cutoff.

Where to go next