On Fedora, the usual firewalld recipe for SSH locks out the very host it is meant to allow. One rich rule accepts the admin host and a second logs and drops everyone else, and on our Fedora 45 Server VM the allowed host was refused three times out of three. At the default priority, firewalld runs every drop rich rule in a zone before any accept rule, and most firewalld surprises come from hidden order like that.
This guide sets up firewalld on Fedora 45 and 44 the way the lab showed it behaves, covering zones for Server and Workstation, services and ports, a custom service, rich rules, an SSH source zone, ipsets, panic mode and firewall-config. Three VMs took part: bench01 (Fedora 45 Server), perf-a (Fedora 45 Workstation) and bench02 (a Fedora 44 client). Adding 1,000 rich rules took 91 seconds where 1,000 ipset entries took 0.21 (firewalld 2.5.2, tested October 2026 on Fedora 45, with Fedora 44 on 2.4.4).
firewalld on Fedora: what the lab measured
The table collects the results that change how you configure the firewall, so the steps below can refer back to them. Each row was measured on bench01, the Fedora 45 Server VM, unless the row says otherwise, and bench02 on Fedora 44 repeated the install, zone, chain order and error checks with the same results.
| Check | Result | What it means for you |
|---|---|---|
| firewalld on the Cloud image | not installed on Fedora 45 or 44 Cloud | install it before you rely on it |
State right after dnf install | enabled, inactive | start it yourself |
| Default zone, Server edition | FedoraServer: cockpit, dhcpv6-client, ssh | port 9090 is open for Cockpit until you remove it |
| Default zone, Workstation edition | FedoraWorkstation: TCP and UDP 1025-65535 open | a dev server on port 8000 answered from the LAN |
| Service definitions shipped | 264 on Fedora 45, 263 on Fedora 44 | check --get-services before writing a port rule |
| Rich rule accept plus rich rule drop on ssh | allowed host blocked 3 of 3 times | drop rules run before accept rules |
| Drop rule with a 10/m log limit | every SSH packet dropped, 18 log lines in 90 s | the limit applies to logging, not to the drop |
| Adding 1,000 rich rules (permanent) | 90.95 s, ruleset grew from 63 to 1,063 rule lines | use an ipset for address lists |
| Loading 1,000 / 10,000 ipset entries | 0.21 s / 0.30 s, ruleset stays at 205 rule lines | size of the list stops mattering |
| Reload time | 0.3 to 0.65 s in every configuration | reloads are cheap, so test before saving |
| Panic mode | 3 of 3 pings lost, new SSH sessions timed out | only start it with a timer that turns it off |
Two rows deserve a second look before you touch a production box. The Workstation zone opens every unprivileged port, which is a deliberate desktop default and a poor one on a laptop that joins hotel Wi-Fi. And the 91 seconds for rich rules is paid when you add them, since the reload afterwards took only 0.65 seconds against 0.6 with no extra rules, yet it is reason enough to never manage a block list one rich rule at a time.
Step 1: Install and start firewalld
A Fedora Server or Workstation install includes firewalld, but the Fedora Cloud images we cloned for this lab had no firewalld package at all, on both releases. Check what you have first, because a minimal or cloud install that looks firewalled may be filtering nothing:
rpm -q firewalld nftables
On the Cloud image the first line reads as missing while nftables is present, which only means the kernel tooling is there with no ruleset loaded.
package firewalld is not installed
nftables-1.1.6-4.fc45.x86_64
Install the package if it is missing. The install pulls in python3-firewall, ipset and iptables-nft, nine packages in total on Fedora 45.
sudo dnf install -y firewalld
The package’s scriptlet enables the unit but does not start it, so systemctl is-active firewalld printed inactive on both releases straight after the install. Start it now. Over SSH this is safe as long as sshd listens on port 22, because every default zone on Fedora allows the ssh service, and our session to the Cloud VM stayed up through the start. If sshd runs on another port, open it first with sudo firewall-offline-cmd --add-port=2222/tcp (your port), which writes the permanent config while the daemon is still stopped:
sudo systemctl enable --now firewalld
Then confirm the daemon answers on D-Bus and see which zone it chose. The first firewall-cmd call after a start printed Waiting on dbus connection... before the version on both releases, which is harmless.
firewall-cmd --version
sudo firewall-cmd --state
sudo firewall-cmd --get-active-zones
On the Server VM the daemon reported running with eth0 in FedoraServer. The full zone listing is in the capture below, and the services line is the one to read: Cockpit is open by default on Server, so if you do not use it, remove it in Step 3.

The default zone is decided once, when the package installs. The scriptlet reads VARIANT_ID from /etc/os-release and points /etc/firewalld/firewalld.conf at one of three shipped files, but only if that symlink does not exist yet:
sudo readlink /etc/firewalld/firewalld.conf
The scriptlet picks the file from the edition it finds:
- Server:
firewalld-server.conf, so the default zone is FedoraServer. - Workstation, Silverblue, KDE and Kinoite:
firewalld-workstation.conf, with FedoraWorkstation as the default. - Everything else, Cloud included:
firewalld-standard.conf, which means public.
When we swapped a Cloud VM’s identity to Server with dnf swap after firewalld was installed, the link stayed on firewalld-standard.conf and the zone stayed public. If you converted an edition that way, set the default zone yourself as shown in Step 2 rather than expecting the package to follow.
Step 2: Pick a firewalld zone on Fedora
A zone is the set of services, ports and rules applied to traffic that arrives on an interface or from a source address. Our Fedora 45 and 44 VMs both listed twelve zones (packages such as libvirt can add more), and their defaults were identical on the two releases:
sudo firewall-cmd --get-zones
We dumped each zone’s target, services and ports on both releases. A target of default rejects whatever the zone does not allow, which a client sees as No route to host.
| Zone | Target | Allowed by default | Use it for |
|---|---|---|---|
| FedoraServer | default | cockpit, dhcpv6-client, ssh | Fedora Server NICs |
| FedoraWorkstation | default | dhcpv6-client, samba-client, ssh, ports 1025-65535 TCP and UDP | a desktop on a network you trust |
| public | default | dhcpv6-client, mdns, ssh | untrusted networks, cloud VMs |
| home, internal | default | dhcpv6-client, mdns, samba-client, ssh | LAN-facing interfaces |
| work | default | dhcpv6-client, mdns, ssh | office networks |
| dmz | default | ssh | an exposed bastion |
| external | default | ssh, masquerade on | the outside NIC of a gateway |
| block | REJECT | nothing | refuse loudly |
| drop | DROP | nothing | refuse silently |
| trusted | ACCEPT | everything | an isolated backend link only |
| nm-shared | ACCEPT | dhcp, dns, ssh | NetworkManager connection sharing |
Pick the zone from where the machine lives, not from the edition you installed. A Workstation on a home LAN can keep FedoraWorkstation, but the same laptop on any network you do not control belongs in public. We started python3 -m http.server 8000 as an ordinary user on the Workstation VM and fetched it from the Fedora 44 client, and it answered with HTTP 200. Every dev server, Jupyter notebook or debug port you start above 1024 is reachable the same way.
The default zone is the zone for interfaces nothing else claims. On a laptop that roams, for example, you would make public the default, and you set it without --permanent because the option already writes both configurations. Skip this on a server you are following the later steps on: they use whatever zone your interface is in.
sudo firewall-cmd --set-default-zone=public
The new default applies at once to every interface that has no zone of its own, and because it is written to both configurations it also survives a reload. Adding --permanent to that command is the one variant that fails.
Error: “Can’t use stand-alone options with other options.”
That message, with exit code 2, is what sudo firewall-cmd --permanent --set-default-zone=public printed on Fedora 45 and 44. The firewall-cmd man page lists --set-default-zone as a runtime and permanent change in one, so it refuses the extra flag. Many older recipes, including the previous version of this page, carry that combination; drop --permanent and the command succeeds.
Bind a NetworkManager connection to a zone
On Fedora the interfaces belong to NetworkManager, and firewalld knows it. When we ran firewall-cmd --permanent --zone=internal --change-interface=eth0 on both releases, firewalld answered that the interface is under control of NetworkManager and wrote the zone into the connection profile instead of a zone file. Set it on the connection directly, so the choice travels with the Wi-Fi network or cable it belongs to:
nmcli -t -f NAME,DEVICE connection show --active
Take the connection name from the first field (on perf-a it was cloud-init eth0; on a laptop it is the Wi-Fi network name) and assign the zone:
sudo nmcli connection modify "cloud-init eth0" connection.zone public
sudo firewall-cmd --get-zone-of-interface=eth0
The change applied live on perf-a with no reconnect. eth0 moved to public, and the same curl from the Fedora 44 client that got HTTP 200 a minute earlier now failed to connect:
public
An empty connection.zone means “use the default zone”, which is why the Workstation VM sat in FedoraWorkstation before. Set the zone per connection on laptops and leave the default alone for the networks you trust.
Step 3: Open services and ports
Steps 3 to 7 ran on bench01, where eth0 sits in FedoraServer, so the outputs show that zone. Find the zone your own interface is in (replace eth0 with your interface from ip -br link) and keep it in a variable, because every command from here on targets it explicitly:
ZONE=$(sudo firewall-cmd --get-zone-of-interface=eth0)
echo "$ZONE"
On bench01 that printed FedoraServer. If you changed the default zone or a connection’s zone in Step 2, this variable picks the change up, which is the point of it. A mistyped interface prints no zone with exit code 2 instead, so check the name, or fall back to ZONE=$(sudo firewall-cmd --get-default-zone). The variable lives only in your current shell, so set it again after every reconnect.
firewalld keeps two configurations. Runtime is what the kernel enforces now, and permanent is what loads at boot and on every reload. A command without --permanent changes only runtime, and a reload throws it away. The lab shows it in four lines. Add http to runtime, compare both views, then reload:
sudo firewall-cmd --zone="$ZONE" --add-service=http
sudo firewall-cmd --zone="$ZONE" --list-services
sudo firewall-cmd --zone="$ZONE" --list-services --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone="$ZONE" --list-services
http appears in runtime, is absent from permanent, and is gone after the reload.
success
cockpit dhcpv6-client http ssh
cockpit dhcpv6-client ssh
success
cockpit dhcpv6-client ssh
That split is useful rather than annoying, because you can try a change in runtime, check it from another machine, and only then save it. For a server you already know the answer for, write straight to permanent and reload once. These lines open HTTP and HTTPS, close Cockpit, and open two raw ports for an app on 8080 and WireGuard on 51820:
sudo firewall-cmd --permanent --zone="$ZONE" --add-service=http --add-service=https
sudo firewall-cmd --permanent --zone="$ZONE" --remove-service=cockpit
sudo firewall-cmd --permanent --zone="$ZONE" --add-port=8080/tcp --add-port=51820/udp
sudo firewall-cmd --reload
Check runtime after the reload, since that is what packets see. The permanent listing would show the same services here, but only the runtime one proves the reload took effect.
sudo firewall-cmd --zone="$ZONE" --list-services
sudo firewall-cmd --zone="$ZONE" --list-ports
Cockpit is gone from the list and both ports are live. Nothing restarted, and the SSH session that ran these commands stayed connected through the reload.
dhcpv6-client http https ssh
8080/tcp 51820/udp
WireGuard has its own service definition (sudo firewall-cmd --info-service=wireguard prints ports: 51820/udp), so --add-service=wireguard would have been the cleaner choice. Prefer a service name whenever one exists, because the zone listing then says what is open and why. To keep a runtime experiment you have already verified, save the whole runtime state in one go:
sudo firewall-cmd --runtime-to-permanent
Use it with care. It copies everything in runtime, including anything a colleague opened by hand last week, and on our VM it saved a stray 9090/tcp test port right next to the rules we meant to keep.
Step 4: Define a custom service
An app that listens on two ports and runs on several machines deserves a service file, because one name in every zone is easier to audit and remove than a list of loose ports. Files in /etc/firewalld/services/ override and extend the 264 shipped definitions. Create one:
sudo vim /etc/firewalld/services/myapp.xml
This is the file we loaded on the Server VM, an internal API on 8443 plus its metrics port.
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>myapp</short>
<description>Internal API on 8443/tcp and its metrics on 9100/tcp</description>
<port protocol="tcp" port="8443"/>
<port protocol="tcp" port="9100"/>
</service>
Validate the permanent configuration before you reload, then load the service and add it to the zone:
sudo firewall-cmd --check-config
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --zone="$ZONE" --add-service=myapp
sudo firewall-cmd --reload
sudo firewall-cmd --info-service=myapp
The check prints success, and the service shows both ports.
myapp
ports: 8443/tcp 9100/tcp
protocols:
source-ports:
modules:
destination:
includes:
helpers:
If you prefer not to write XML, firewall-cmd builds the same file for you. These commands produced /etc/firewalld/services/myapp2.xml with a single <port port="7000-7002" protocol="tcp"/> line:
sudo firewall-cmd --permanent --new-service=myapp2
sudo firewall-cmd --permanent --service=myapp2 --add-port=7000-7002/tcp
sudo firewall-cmd --permanent --service=myapp2 --set-short="myapp2"
Run sudo firewall-cmd --reload afterwards so the new service becomes usable in zones. The generated file has no description element, so add one by hand if other admins will read it.
Error: “INVALID_PORT: ’91OO’ is invalid port range”
We broke the file on purpose by typing the letter O in place of zeros. --check-config caught it with exit code 102, and so did --reload. The reload refused the new configuration and left the previous rules running, so firewalld stayed active and --list-services still showed myapp. So a typo in a service file does not drop the firewall, but your latest change is not live either. Both commands print the error and exit non-zero, and the danger is a script or config-management run that ignores the exit code. The same error lands in journalctl -u firewalld, which is the place to look when a reload “worked” and the port is still closed.
Step 5: Write rich rules that work
Rich rules filter on source, destination, service, port, logging and action in one line. The firewalld rich language reference documents every element. What the syntax hides is placement, since firewalld does not run rich rules in the order you add them. Each zone is a chain that jumps through _pre, _log, _deny, _allow and _post sub-chains, in that order, and a rule’s action decides its sub-chain. At the default priority of 0, every drop or reject therefore runs before every accept in the same zone, whatever order they were added in.
We tested the common “allow the admin host, log and drop everyone else” recipe with that in mind. The --timeout=90 option makes both rules runtime-only and removes them after 90 seconds. That is the safest way to try any SSH rule on a remote box, because a lockout lasts a minute and a half.
sudo firewall-cmd --zone="$ZONE" --timeout=90 --add-rich-rule='rule family="ipv4" source address="192.168.1.144/32" service name="ssh" accept'
sudo firewall-cmd --zone="$ZONE" --timeout=90 --add-rich-rule='rule family="ipv4" service name="ssh" log prefix="ssh-deny " level="warning" limit value="10/m" drop'
The nftables view explains what happened next. The drop sits alone in filter_IN_FedoraServer_deny, and the zone jumps there before it ever reaches the accept in _allow:

From 192.168.1.144, the one host the first rule allows, three connection attempts to port 22 all failed. A new SSH session from our admin machine timed out as well. Only already-open sessions survived, because firewalld accepts established connections before any zone chain runs. The Fedora 44 client showed the same _pre, _log, _deny, _allow, _post order, so this is not new behaviour.
The log part misleads too. In that rule, limit value="10/m" follows log, so it limits how many log lines are written, not how many packets are dropped. Every SSH packet was dropped, and the journal got 18 ssh-deny lines over the 90 seconds. Those lines are kernel messages, so read them with sudo journalctl -k -g ssh-deny. Our Fedora 45 VMs had no /var/log/messages at all, since nothing writes one unless you install rsyslog.
Rich rules that work as written fall into four patterns. We ran the source zone in Step 6; the other three follow the rich language reference and were not part of the lab run:
- Accept only: remove the service from the zone and add an accept rich rule per allowed source. Everything else falls through to the zone target, which rejects it.
- Drop with an exception in the rule:
source not address="192.168.1.144/32"inside the drop rule keeps the admin host out of it. - A negative priority on the accept: the reference places a rule with
prioritybelow 0 in the_prechain, sorule priority="-1" family="ipv4" source address="192.168.1.144/32" service name="ssh" acceptis evaluated before the drop. - A source zone: move the admin addresses into their own zone that allows ssh, and take ssh out of the main zone. Step 6 does this, and it is the one we would run.
A plain address block needs no service element. This drops a hostile documentation range outright, and you can keep it for good with --permanent plus a reload:
sudo firewall-cmd --permanent --zone="$ZONE" --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" drop'
sudo firewall-cmd --reload
For more than a handful of ranges, skip rich rules and use the ipset in Step 7. The numbers there show why, since 1,000 rich rules took 91 seconds just to add.
Step 6: Limit SSH with a source zone
At the default ingress priority of 0, firewalld matches source addresses before interfaces, so a zone bound to your admin addresses captures their traffic no matter which interface it arrives on. Before touching SSH on a remote server, arm a timer that reloads the permanent configuration in five minutes. Runtime changes that lock you out are undone by that reload, and you cancel the timer once you have tested:
sudo systemd-run --on-active=300 --unit=fw-undo /usr/bin/firewall-cmd --reload
Create the zone with your admin hosts as sources (ours were bench02 at 192.168.1.144 and our admin machine at 192.168.1.10; a whole subnet such as 10.0.1.0/24 works the same way), allow ssh in it, and reload. The main zone still allows ssh at this point, so the reload cannot lock anyone out:
sudo firewall-cmd --permanent --new-zone=mgmt
sudo firewall-cmd --permanent --zone=mgmt --add-source=192.168.1.144/32
sudo firewall-cmd --permanent --zone=mgmt --add-source=192.168.1.10/32
sudo firewall-cmd --permanent --zone=mgmt --add-service=ssh
sudo firewall-cmd --reload
Now remove ssh from the main zone in runtime only. If this goes wrong, the timer brings it back:
sudo firewall-cmd --zone="$ZONE" --remove-service=ssh
Test from both sides before saving. From bench02 (in mgmt) SSH opened, and from perf-a (not in mgmt) the client got connect: No route to host, which is the reject of the server’s main zone. Our admin session reconnected fine, so we saved runtime and stopped the timer. If you test by reconnecting too, set ZONE again in the new shell before the next command:
sudo firewall-cmd --runtime-to-permanent
sudo systemctl stop fw-undo.timer
The result, as it looked on the Server VM after a reboot into the newer kernel. Both sources and the trimmed service list survived the reboot, so the saved configuration holds.

One side effect caught us out. After the change, bench02 could no longer reach the web app on 8080 that FedoraServer allows, while perf-a still could. A packet from a source in mgmt is handled by mgmt alone, and the zone’s default target rejects anything mgmt does not list, so the admin hosts lose every service the main zone offers. Add those services to mgmt as well (--zone=mgmt --add-service=http, for example), or bind a subnet only when its hosts need nothing else from the server. Note too that a new zone gets forward: no, while every built-in zone except nm-shared shows forward: yes.
You can still see who knocks on port 22 from outside mgmt by adding a rich rule that logs without an action. It lands in the _log chain, and the zone’s reject still does the refusing:
sudo firewall-cmd --permanent --zone="$ZONE" --add-rich-rule='rule family="ipv4" service name="ssh" log prefix="ssh-outside-mgmt " level="info" limit value="6/m"'
sudo firewall-cmd --reload
We fired 20 connection attempts from perf-a and counted 6 log lines, which matches the limit. Each line carries the source address and port, enough for fail2ban or a quick audit:
Oct 26 00:48:56 bench01 kernel: ssh-outside-mgmt IN=eth0 OUT= MAC=bc:24:11:36:f9:86:bc:24:11:65:a4:19:08:00 SRC=192.168.1.136 DST=192.168.1.125 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=57959 DF PROTO=TCP SPT=37204 DPT=22 WINDOW=64240 RES=0x00 SYN URGP=0
On a Workstation that needs SSH at all, our guide to enabling the SSH server on Fedora pairs well with this zone, and the Fedora hardening guide adds key-only login and fail2ban on top.
Step 7: Block address lists with an ipset
An ipset is one nftables set that holds thousands of addresses, matched with a hash lookup instead of one rule per address. We measured the difference with 1,000 /24 networks on the Server VM. Adding them as permanent rich rules in one firewall-cmd call took 90.95 seconds and grew the loaded ruleset from 63 to 1,063 rule lines (we counted lines of nft list ruleset ending in accept, drop or reject). Loading the same 1,000 networks into an ipset took 0.21 seconds and the next 9,000 took 0.30. The count went to 205 with the set bound to the drop zone and stayed there at 10,000 entries, so the jump comes from activating the drop zone’s chains, not from the addresses.
Create a hash:net set and fill it from a plain text file with one address or CIDR per line, such as a feed you download to /root/blocklist.txt. The maxelem of 65536 matches the ipset default, so raise it if your feed is bigger.
sudo firewall-cmd --permanent --new-ipset=blocklist --type=hash:net --option=maxelem=65536
sudo firewall-cmd --permanent --ipset=blocklist --add-entries-from-file=/root/blocklist.txt
Send the whole set to the drop zone as a source, so its traffic is discarded without a reply, and reload:
sudo firewall-cmd --permanent --zone=drop --add-source=ipset:blocklist
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --ipset=blocklist --get-entries | wc -l
The reload took 0.32 to 0.34 seconds with 1,000 entries and 0.41 to 0.49 seconds with 10,000, in the same range as the 0.6 seconds an empty configuration needed, so the list size does not slow your changes down. Update it the same way when your feed refreshes, and remove a range with --permanent --ipset=blocklist --remove-entry; permanent entry changes go live at the next reload. To undo the whole thing, run --permanent --zone=drop --remove-source=ipset:blocklist, then --permanent --delete-ipset=blocklist, then reload, which is the order we cleaned up the lab in.
Step 8: Audit the rules in firewall-config
firewall-config is the GTK front end for the same daemon, and its value is review rather than editing. Every zone, service, ipset and rich rule sits in one window with the active bindings on the left. It is a separate package, so install it if your app grid has no Firewall entry:
sudo dnf install -y firewall-config
Launch it from the app grid as “Firewall” or run firewall-config. The left pane shows which connection sits in which zone, and the status bar at the bottom shows the default zone, LogDenied and panic state, three things the CLI needs three commands for:

The Rich Rules tab lays each rule out in columns, which is far easier to scan than the one-line syntax. The capture shows the accept rule we added to perf-a’s public zone for it, limited to one client with sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.144/32" port port="8000" protocol="tcp" accept' and a reload:

The Configuration drop-down at the top switches between Runtime and Permanent, with the same meaning as on the CLI. A Runtime edit vanishes at the next reload, exactly like a firewall-cmd change without --permanent, while a Permanent edit is saved but not live until you reload from the Options menu. We used the GUI for review only, so those editing paths were not part of the lab run.
Step 9: Panic mode and the nftables ruleset
Panic mode drops every packet in and out of the host. During our test the Fedora 44 client lost 3 of 3 pings and could not open port 22, and a new SSH session from our admin machine timed out. So never type --panic-on over SSH by hand. Run it under a transient unit that turns it off again, here after 30 seconds:
sudo systemd-run --unit=panic-test /bin/bash -c "firewall-cmd --panic-on; sleep 30; firewall-cmd --panic-off"
Once the 30 seconds pass, confirm it is off. The unit’s journal shows both calls succeeding 30 seconds apart.
sudo firewall-cmd --query-panic
sudo journalctl -u panic-test --no-pager
For incident response at a console, plain sudo firewall-cmd --panic-on is fine. For a remote host, the drop zone with your mgmt source zone still in place is usually the better choice, because it cuts everyone off except you.
Under all of this, firewalld writes one nftables table, inet firewalld, and owns it (NftablesTableOwner=yes in /etc/firewalld/firewalld.conf). Read it when a port you opened still refuses connections, because the zone chains show exactly which sub-chain a rule landed in:
sudo nft list tables
sudo nft list chain inet firewalld filter_IN_mgmt_allow
On the Server VM the table list held only table inet firewalld, and the mgmt allow chain held a single rule.
table inet firewalld {
chain filter_IN_mgmt_allow {
tcp dport 22 accept
}
}
Don’t try to edit that table with nft. With table ownership on, nft cannot change it, and even with ownership off the next reload would replace your edits. If other software (Docker, Podman, libvirt) needs its own rules, it adds its own table, and our Podman on Fedora guide shows how its published ports interact with firewalld zones.
Fedora 45 and Fedora 44 firewalld differences
Fedora 44 repeated the install, the zone defaults, the set-default-zone error, the NetworkManager hand-off, the rich-rule chain order and the config check, with the same results as Fedora 45. The mgmt zone, ipset, custom service and panic tests ran on Fedora 45 only. The differences we found are in versions and in what is shipped.
| Item | Fedora 45 | Fedora 44 |
|---|---|---|
| firewalld | 2.5.2 | 2.4.4 |
| nftables | 1.1.6-4.fc45 | 1.1.6-2.fc44 |
| Service definitions | 264 | 263 |
| Zones after install | 12 | 12 |
| Shipped policies | allow-host-ipv6 plus 5 gateway policies, disabled | same |
| Zone chain order | pre, log, deny, allow, post | same |
| Firewall backend | nftables | nftables |
The extra service on Fedora 45 is Solr, added upstream in the 2.5 line. The gateway policies matter more if you are building a router. Both releases ship five of them, disabled, and each one names its ingress and egress zones:
- gateway-lan-to-world: accepts forwarded traffic from internal, home and trusted out to external and public.
- gateway-lan-to-HOST: lets the LAN zones reach dhcp, dns and ssh on the gateway itself.
- gateway-lan-to-work: accepts forwarded traffic from the LAN zones into work.
- gateway-dmz-to-HOST and gateway-world-to-HOST: allow dhcp and dns from the dmz, and dhcpv6-client from outside.
One command enables the whole set, sudo firewall-cmd --permanent --policy-set=gateway --remove-disable, and --add-disable turns it off again; both need a reload to take effect. We toggled it on the Workstation VM and watched the policies switch to disable: no. That VM has a single NIC, though, so we did not route traffic through it, and IP forwarding still read 0 afterwards.
If you are on Fedora 44 and planning the jump, follow the Fedora 44 to 45 upgrade guide and then rerun sudo firewall-cmd --check-config and --list-all to confirm your zones came across. On a fresh box, set the firewall as part of the first things to do after installing Fedora 45, and for Rocky Linux or RHEL hosts the same zones and commands are covered in our guide to configuring firewalld on Rocky Linux and RHEL.