linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Simulator
  • Knowledge base
  • Interview prep
Index
Categories
All entries
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
home/linux/kb/Security/firewalld-vs-nftables

kb/security ── Security ── intermediate

firewalld vs nftables: what to choose

firewalld is a daemon wrapper with zones, services, and rich rules; the backend since RHEL 8 is nftables. Plain nft gives more control, sets, and atomic reload. firewalld fits desktop and multi-zone, nft fits a server fleet.

view as markdownaka: firewalld, firewall-cmd, nft-direct, rich-rules, firewall-zones

Why choose

On Linux in 2026 there are four current ways to manage netfilter:

ToolingLevelWhere it is default
cmd-iptableslow, legacyUbuntu LTS, embedded, Docker (still)
cmd-nftlow, modernDebian 11+, Ubuntu 22+, direct kernel API
firewalldhighRHEL/CentOS/Fedora server
ufwhighUbuntu desktop

iptables vs nftables is a technical dilemma (legacy vs modern, rules-replay vs atomic). firewalld vs plain nft is an architectural dilemma: do you orchestrate yourself, or does a daemon do it. In this comparison ufw is a simplified firewalld for Ubuntu.

What firewalld is

A daemon that reads XML config files and applies them to netfilter through a backend (since RHEL 8 that is nftables, before that iptables):

/usr/share/firewalld/             ← distribution default
/etc/firewalld/                    ← customization
├─ firewalld.conf                  ← global config
├─ zones/                          ← XML files describing zones
│   ├─ public.xml
│   ├─ trusted.xml
│   └─ work.xml
├─ services/                       ← XML files describing services
│   ├─ ssh.xml
│   └─ http.xml
└─ icmptypes/

Commands go through firewall-cmd:

bash
firewall-cmd --state                       # is it running
firewall-cmd --get-active-zones
firewall-cmd --get-default-zone
firewall-cmd --list-all                    # the current zone in detail
# Open HTTP permanently
firewall-cmd --permanent --add-service=http
firewall-cmd --reload
# An arbitrary port
firewall-cmd --permanent --add-port=8080/tcp
# Remove
firewall-cmd --permanent --remove-service=http

--permanent writes to XML; without it the change is runtime-only and is lost on reload. A common pattern: first test without --permanent, then run --runtime-to-permanent to commit.

Zones, the firewalld concept

Every network interface or source IP belongs to a zone. A zone is a trust level plus a list of allowed services and ports.

Default zones:

ZoneBy default
dropdrop everything, no replies
blockreject everything (with ICMP unreachable)
publicdefault; ssh and dhcpv6-client allowed
externalfor NAT, MASQUERADE enabled
dmzssh allowed; for an external interface facing DMZ machines
workssh, dhcpv6-client, and mdns allowed
homeplus samba and mdns; for a home network
internalsame as home
trustedeverything allowed

Binding an interface:

bash
firewall-cmd --zone=trusted --change-interface=eth1 --permanent

Binding a source IP:

bash
firewall-cmd --zone=trusted --add-source=10.0.0.0/24 --permanent

Sources take priority over interfaces. If the source falls into zone X, the traffic follows the rules of X regardless of the interface.

This is a strong point of firewalld: the same server can apply different rules to different networks without a complex rule cascade.

Rich rules, customization in firewalld

When services and ports are not enough, there are rich rules, a DSL for complex rules:

bash
firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4"
  source address="10.0.0.0/24"
  service name="http"
  log prefix="http-from-internal: " level="info"
  accept'
firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4"
  source address="1.2.3.4"
  drop'
firewall-cmd --permanent --add-rich-rule='
  rule
  service name="ssh"
  accept
  limit value="3/m"'                       # rate-limit

Handy: ipset-style matching, rate-limit, log, and accept/reject/drop. Under the hood it still compiles to nftables.

Plain nft, Pro

Direct control:

nft
# /etc/nftables.conf
table inet filter {
    set blocked_ips {
        type ipv4_addr
        flags interval
        elements = { 1.2.3.0/24, 5.6.7.8 }
    }
    chain input {
        type filter hook input priority filter; policy drop;
        ct state established,related accept
        iif lo accept
        ip saddr @blocked_ips drop
        tcp dport { 22, 80, 443 } accept
        ip protocol icmp accept
        counter log prefix "DROPPED: " drop
    }
    chain forward {
        type filter hook forward priority filter; policy drop;
    }
}

Apply:

bash
nft -f /etc/nftables.conf
systemctl enable --now nftables

Advantages over firewalld:

  • Atomic reload: the whole file in one transaction, with no race window
  • Sets and maps, natively (vlan-id to action, ip to action)
  • One config in git, version controlled
  • Fewer abstractions: what you wrote is what lands in the kernel
  • Counters and log inline, with no extra plumbing
  • Less overhead: there is no daemon

Drawbacks:

  • No zones or services abstraction, everything is by hand
  • No API for dynamic redrawing (each change is a file edit plus reload)
  • Harder for junior engineers

When to choose what

firewalld:

  • Desktop or laptop on different networks (home, work, coffee shop)
  • A multi-tenant server with zones by trust
  • RHEL production where admins are used to firewall-cmd
  • You need integration with NetworkManager (NM zones)
  • Dynamic rules through an API or D-Bus from applications

Plain nftables:

  • A server fleet with config management (Ansible, Puppet)
  • One config for a group of servers
  • Performance-sensitive loads (gateway, k8s nodes)
  • Complex rule sets with sets, maps, and vmaps
  • Custom NAT and mangle rules

iptables-legacy:

  • Only if something old does not work with nft
  • Docker still writes its rules through iptables, so leave it there

ufw:

  • The simple case of "allow SSH, allow HTTPS, drop everything else" on a single machine; for anything more, use firewalld or nft

Migration firewalld to nft

bash
firewall-cmd --runtime-to-permanent          # commit everything to XML
systemctl stop firewalld
systemctl disable firewalld
systemctl mask firewalld
nft list ruleset > /etc/nftables.conf       # export
# Clean up, add a header, skip .data {} and .options {}
systemctl enable --now nftables

Check that everything is open as before: nft list ruleset.

firewalld backend toggle

You can temporarily revert to the iptables backend if nft breaks something:

ini
# /etc/firewalld/firewalld.conf
FirewallBackend=iptables                    # default is nftables
bash
systemctl restart firewalld

Use this only if a specific case breaks on the nftables backend.

When things go wrong

  • firewall-cmd: not running: the service is not started. Run systemctl start firewalld. Check whether it is masked (systemctl unmask firewalld).
  • Changes disappear after reload: you forgot --permanent.
  • Service conflicts: another firewall tool (iptables-services, ufw, nftables.service) is already running. Mask the extra ones.
  • NAT does not work in the external zone: masquerade is enabled by default only in external. In public, run firewall-cmd --zone=public --add-masquerade.
  • nft: Could not process rule: No such file or directory: a syntax error in the config; nftables-services does not load. Check nft -c -f /etc/nftables.conf (dry run).
  • Docker broke after an nft restart: Docker writes its own rules to iptables/nft at start. Restart systemctl restart docker after the firewall.
  • You opened a port but it does not work: you forgot firewall-cmd --reload after --permanent. Or the wrong zone (check --get-active-zones).

Comparison table

Featurefirewalldnftables (plain)cmd-iptablesufw
Styledeclarative XML + CLIdeclarative fileimperative CLIdeclarative CLI
Backendnftables (default) or iptablesnftables (kernel)iptables (kernel xt_)iptables
Zonesyesnonono
Atomic reloadthrough restartyesnono
Rich rules / DSLrich-rulesnft DSLshell-onlyrules.before
API / D-Busyesnonono
Runtime vs persistentseparatedoneby hand iptables-saveone
Default onRHEL/FedoraDebian/Ubuntu modernembedded, Alpine, legacyUbuntu desktop
Complexitymediumlow (if you know nft)mediumvery low

§ команды

bash
firewall-cmd --list-all

The current zone and all its rules, the first thing to check

bash
firewall-cmd --get-active-zones

Which zones are active and on which interfaces or IPs

bash
firewall-cmd --permanent --add-port=8080/tcp && firewall-cmd --reload

Open a port permanently, the typical flow

bash
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=1.2.3.4 reject'

Block a specific IP through a rich rule

bash
nft list ruleset

The effective state of kernel netfilter, no matter how it was set up

bash
nft -c -f /etc/nftables.conf

Dry run the nft config before applying it, a syntax check without apply

bash
systemctl mask firewalld nftables iptables

Block competing firewall services, you cannot run them at the same time

§ см. также

  • cmd-nftnft: modern firewall (nftables)`nft` is the single CLI for modern netfilter. Replaces iptables/ip6tables/ arptables/ebtables. Structure: tables, chains, rules.
  • cmd-iptablesiptables: netfilter rules (legacy)iptables is the userland interface for netfilter. Five tables (filter/nat/mangle/raw/security), chains INPUT/OUTPUT/FORWARD/PRE/POSTROUTING, and jump targets ACCEPT/DROP/MASQUERADE. Legacy, but still widely deployed.
  • natNAT: Network Address TranslationNAT rewrites the src or dst address of a packet at a router. Masquerade is the common case: the src IP is replaced with the router's outbound address so hosts on a private network can reach the public internet.
  • ip-forwardingIP Forwarding: Turn a Host into a RouterLinux does not forward packets between interfaces by default. Enable it with `sysctl net.ipv4.ip_forward=1`. Without this, NAT, VPN routing, and any forwarding will not work.
  • cis-benchmark-hardeningCIS Benchmark and system hardening (lynis, OpenSCAP)CIS Benchmark is the Linux hardening standard. Lynis is a fast local audit with a score, OpenSCAP is the formal one with XCCDF profiles and a SCAP report. ansible-lockdown remediates. Keep audit and remediate separate.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies