skip to content

On a host managed by firewalld or ufw, you add your own rules with `nft add rule`. What happens to those rules, and how should hand-written nftables rules coexist with a firewall frontend?

level: seniorimportance: nice to knowfreq 33%

answer

  1. the frontend regenerates what it owns
  2. kernel state is not configuration
  3. separate table, still evaluated
  4. drop is final; accept is local
  5. one owner per host, written down

basics

~20 s

Rules added inside a frontend's own table are erased the next time it rebuilds them; rules in a separate table of your own survive, but then both rulesets are evaluated and a drop anywhere wins. Express the rule through the frontend, or manage the ruleset yourself — do not mix.

solid answer

~50 s

firewalld and ufw both generate their ruleset from their own configuration and rewrite it wholesale on reload or restart. firewalld owns a `firewalld` table it regenerates on `firewall-cmd --reload` and at service start; ufw drives the `iptables` command (itself the nft-backed one on current Ubuntu) from files under `/etc/ufw` and flushes its chains on `ufw reload`. Anything you injected into their objects with `nft add rule` is gone at that point, with no warning. A table of your own is a different matter: netfilter evaluates every base chain registered on a hook, so your table runs alongside theirs — but the outcome is the intersection, since `accept` only ends the current chain while `drop` is final. Your accept cannot override their drop, and priority orders chains rather than deciding authority. So the right answers are: put the rule where the frontend expects it (`firewall-cmd --permanent` rich rules, `/etc/ufw/before.rules`), or disable the frontend and own `/etc/nftables.conf` yourself.

code

bash · 10 lines
bash
# firewalld: express it in firewalld's own vocabulary, permanently
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="5432" protocol="tcp" accept'
firewall-cmd --reload
nft list ruleset            # read-only: see what firewalld generated

# or take ownership instead
systemctl disable --now firewalld
nft -c -f /etc/nftables.conf && nft -f /etc/nftables.conf
systemctl enable --now nftables

go deeper

for a junior

Know that firewalld and ufw generate the kernel rules from their own configuration, so changes must be made with firewall-cmd or in /etc/ufw rather than by adding rules directly.

for a middle

Explain why a rule injected with nft disappears on reload, and where the persistent equivalent lives for each frontend — permanent rich rules for firewalld, before/after rules for ufw.

for a senior

Reason about several tables on one hook: all base chains run, drop is final, accept is local, and priority only orders. Use that to explain a port that stays reachable despite the host firewall.

for a principal

Set a single ownership model per host class and enforce it, weighing the frontend's zone abstraction against a generated nftables ruleset with sets and maps, and account for runtimes that install tables of their own.

## The frontends own their ruleset firewalld and ufw are not thin wrappers that hand your rules to the kernel and step back. Each keeps a declarative configuration — zones, services and rich rules for firewalld; profiles and rule files for ufw — and *generates* the kernel ruleset from it. Generation is not incremental: on reload, restart, or a change that requires it, the tool tears down what it owns and builds it again from its configuration. That is the whole answer to the first half of the question. A rule you appended into firewalld's chains with `nft add rule` exists only in the kernel; it is in no configuration file the tool reads. The next `firewall-cmd --reload`, package update or reboot rebuilds those chains and your rule is gone, with nothing logged to explain it. The same holds for ufw's chains and `ufw reload`. ## firewalld specifics Modern firewalld uses the nftables backend (selected by `FirewallBackend=` in `/etc/firewalld/firewalld.conf`, and the default on current RHEL-family and Fedora systems) and builds a table named `firewalld` in the `inet` family with its own base chains. `nft list ruleset` shows the whole thing, which is genuinely useful for understanding what a zone actually did — but reading it is not the same as being allowed to edit it. To make a rule stick, express it in firewalld's own vocabulary and mark it permanent: ```bash firewall-cmd --permanent --zone=public --add-service=https firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="5432" protocol="tcp" accept' firewall-cmd --reload ``` Without `--permanent` the change applies to the running configuration only and is itself lost on reload — the mirror image of the same trap. ## ufw specifics ufw is an iptables frontend. On current Ubuntu the `iptables` command it drives is the nft-backed one, so ufw's rules do appear in `nft list ruleset`, but ufw itself thinks and writes in `iptables-restore` syntax. Its rule files live in `/etc/ufw`, and that is where custom rules belong: `/etc/ufw/before.rules` for rules that must be evaluated before ufw's generated ones, `/etc/ufw/after.rules` for the tail. Because those files are inputs to the generator rather than kernel state, they survive `ufw reload` and reboots. ## Coexistence semantics when you keep your own table Suppose you refuse to use the frontend's vocabulary and instead create `table inet mypolicy` with its own base chain. Nothing stops you, and the frontend will not delete it, because it only manages its own objects. What you need to understand is how the two interact: - Every base chain registered on a hook is evaluated, in ascending priority order. - `accept` terminates evaluation **of that chain** and the packet proceeds to the next base chain on the hook. - `drop` is immediate and final. So the effective policy is the intersection: a packet must be dropped by nobody. Your accept cannot rescue traffic the frontend drops, and the frontend's accept cannot rescue traffic you drop. Choosing a lower priority number makes your chain run *earlier*, not *decisively* — the only way to end evaluation from an earlier chain is to drop. This is also the shape of the well-known surprise where a container runtime's published ports remain reachable despite a host firewall: the runtime installs its own tables and chains, and whether traffic survives depends on what each of them does at which hook — not on which tool is considered "the firewall". ## The practical rule Pick one owner per host and write it down: - **Frontend owns the firewall** — every rule goes through `firewall-cmd --permanent` or `/etc/ufw/*.rules`, and `nft` is used read-only, for `nft list ruleset` during an incident. This is the right default on a host where other people or other tooling also manage firewall state. - **You own the firewall** — disable and mask the frontend (`systemctl disable --now firewalld`, `ufw disable`), manage `/etc/nftables.conf` as a reviewed artefact, and enable the `nftables` service to load it at boot. This is the right choice when the ruleset is generated from configuration management and needs sets, maps and structure the frontend cannot express. The failure mode to avoid is the third option nobody chooses deliberately: half the policy in the frontend's configuration and half in ad-hoc `nft` commands, where a routine reload silently removes protection and the surviving half is impossible to reason about.

  • Your own table accepts port 8080 while firewalld's table drops it. Which wins, and why can you not fix it with priority?
    The drop wins. Every base chain on the hook is evaluated; `accept` merely ends the current chain, while `drop` discards the packet immediately. A lower priority number makes your chain run earlier, not authoritatively — the only verdict that ends evaluation for good is a drop. To open the port you must change the ruleset that drops it.
  • Where do custom rules belong on a ufw-managed Ubuntu host so a reload does not lose them?
    In `/etc/ufw/before.rules` or `/etc/ufw/after.rules`, written in iptables-restore syntax — before for rules that must precede ufw's generated ones, after for the tail. Those files are inputs to ufw's generator, so they are re-applied on every reload and at boot, unlike anything injected straight into the kernel with nft or iptables.
  • How would you decide between keeping firewalld and managing /etc/nftables.conf directly?
    Keep firewalld where its zone model matches the need, other tooling expects it, and operators are used to `firewall-cmd`. Take direct ownership when the policy needs sets, maps or concatenations the frontend cannot express, or when the ruleset is generated from configuration management and must be reviewed as one artefact. What you must not do is run both as sources of truth.

saying these in an interview costs you the question

  • Assumes nft-added rules persist across a firewalld reload
  • Thinks an accept in one table overrides another's drop
  • Believes a lower priority chain overrules later ones
  • Uses firewall-cmd without --permanent and expects persistence
  • Runs a frontend and hand-written rules as joint owners

context