How to Block an IP Address with firewalld on Fedora

Block a specific IP address on Fedora by adding a rich rule to firewalld and reloading the service.

The scenario

You notice a sudden spike in failed SSH logins from a single foreign IP. Or a misconfigured IoT device is hammering your local printer. You need to cut off that traffic immediately. You open the terminal and type an iptables command. That works on older setups, but Fedora uses firewalld by default. The iptables commands will vanish after a reboot or a firewall restart. You need a method that survives system updates and aligns with Fedora's dynamic firewall architecture.

How firewalld actually handles blocks

firewalld does not store rules as a flat list. It uses zones. Each network interface belongs to a zone, and each zone defines what traffic is allowed. When you add a rule, you are modifying the zone configuration. The firewall maintains two separate states. The runtime configuration applies immediately to active connections. The permanent configuration loads on boot and survives service restarts. If you only change the runtime state, the rule disappears when the service restarts. If you only change the permanent state, the rule sits dormant until you reload or reboot. You must write to the permanent store and then push it into the runtime state. Think of it like editing a recipe book versus actually cooking the meal. You update the book, then you tell the kitchen to start using the new recipe.

Fedora's firewalld uses nftables as its backend since version 35. The firewall-cmd tool translates your high-level requests into nft rulesets. This abstraction keeps your configuration stable across kernel updates. You never need to touch nft directly unless you are writing a custom firewall replacement. Trust the package manager. Manual file edits drift, snapshots stay.

The permanent block command

Here is how to add a rich rule that permanently rejects traffic from a specific IPv4 address.

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100" reject' # --permanent writes to the config file so the rule survives reboots
sudo firewall-cmd --reload # --reload pushes the permanent config into the active runtime state without dropping existing connections

Replace 192.168.1.100 with the actual address you want to block. The reject action sends a TCP RST or ICMP unreachable message back to the sender. This is faster than drop, which silently discards packets and forces the sender to wait for a timeout. Use reject when you want the remote host to know the connection was refused immediately. Use drop when you want to hide the fact that your system is even listening.

If you need to block an IPv6 address, the syntax changes slightly. The family attribute must match the address format.

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv6" source address="2001:db8::1" reject' # Matches the IPv6 family to prevent parser errors
sudo firewall-cmd --reload # Applies the IPv6 rule to the active firewall state

The rich rule parser expects exact XML-style quoting. A missing quote or a misplaced space will break the command. Always wrap the entire rule string in single quotes. This prevents the shell from interpreting the double quotes inside the rule.

Temporary blocks for live testing

You do not always need a permanent rule. Sometimes you are troubleshooting a noisy neighbor or testing a security policy. Adding a runtime-only rule lets you verify the block works before committing it to disk.

sudo firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.1.100" reject' # Omits --permanent so the rule lives only in memory
sudo firewall-cmd --list-rich-rules # Confirms the temporary rule is active in the current session

The rule disappears after a reboot or a firewall-cmd --reload. This is useful for live debugging. If the rule causes unintended side effects, a simple reboot restores the previous state. If the rule works as expected, run the permanent command from the previous section to make it stick. Test in a staging environment first if you are managing a production server.

Verify the rule is active

Run this command to confirm the rule is loaded and targeting the correct zone.

sudo firewall-cmd --list-rich-rules # Shows all active rich rules in the default zone

You should see the exact rule string you just added. If you manage multiple zones, specify the zone explicitly with --zone=public or whatever zone your interface uses. The output will confirm whether the rule is active in the runtime state. If the rule is missing, the --reload step failed or was skipped. Run the reload command again before troubleshooting further.

Check which zone your interface actually belongs to. Fedora assigns the public zone to most Ethernet interfaces by default. Wi-Fi often uses home or work. If you add the rule to public but your interface is on home, the traffic passes through untouched.

sudo firewall-cmd --get-active-zones # Lists zones and the interfaces attached to them

Match the zone name to your rule. If your interface is on home, add --zone=home to your commands. The firewall only applies rules to the zone the traffic enters. Run journalctl -xeu firewalld if you suspect the service failed to apply the configuration. The x flag adds explanatory text and the e flag jumps to the end. Most sysadmins type this muscle-memory style when debugging network services.

Common pitfalls and exact error messages

The most frequent mistake is mixing up reject and drop syntax. The rich rule parser is strict. If you type rule family="ipv4" source address="192.168.1.100" drop without the correct XML-style quoting, firewall-cmd will refuse to parse it.

Error: INVALID_RICH_RULE: rule family="ipv4" source address="192.168.1.100" drop

The error appears because the parser expects the action keyword to match the rich rule schema. The correct syntax wraps the action in its own element or uses the exact keyword. Always wrap the entire rule in single quotes. A missing quote or a space in the wrong place triggers the parser error.

Another common issue is targeting the wrong zone. If you see Error: COMMAND_FAILED: 'iptables-restore' failed, your permanent configuration contains a syntax error that prevents the firewall from loading. Fix the malformed rule in /etc/firewalld/zones/ or remove it with --remove-rich-rule before reloading. Never edit the XML files in /usr/lib/firewalld/. Those ship with the package and will be overwritten on update. Edit /etc/firewalld/ only.

If the firewall service itself is dead, firewall-cmd will return Failed to connect to proxy: Connection refused. Start the service before adding rules.

sudo systemctl enable --now firewalld # Enables the service at boot and starts it immediately
sudo systemctl status firewalld # Confirms the service is active and running

Check the status before you restart. The status output shows recent log lines and the current state in one view. Run journalctl -xe first. Read the actual error before guessing.

When to use rich rules versus alternatives

Use rich rules when you need fine-grained control over specific IPs, ports, or protocols within a single zone. Use zone assignment when you want to change the entire trust level of a network interface. Use iptables-nft directly when you are writing a custom firewall script that replaces firewalld entirely. Use ufw when you prefer a simpler syntax and do not need dynamic zone switching. Stay on firewalld rich rules if you are managing a standard Fedora desktop or server and need persistent, reload-safe blocks.

Where to go next