You can replace your entire nftables ruleset without dropping a single packet.
Here’s how it works, using a hypothetical scenario where we’re moving from a basic allow-all inbound SSH policy to a more restrictive one that only allows SSH from a specific IP address.
Let’s say our current ruleset, loaded with nft -f current_rules.nft, looks like this:
table ip filter {
chain input {
type filter hook input priority 0; policy accept;
# Allow all inbound SSH
tcp dport ssh accept
}
}
And our desired new ruleset, new_rules.nft, looks like this:
table ip filter {
chain input {
type filter hook input priority 0; policy accept;
# Allow SSH only from 192.168.1.100
tcp dport ssh ip saddr 192.168.1.100 accept
}
}
The key to atomic replacement lies in nftables’ nft -f - command combined with careful staging. Instead of directly replacing the active ruleset, we’ll stage the new ruleset and then atomically swap it in.
First, let’s verify our new ruleset for syntax errors without applying it:
nft -c -f new_rules.nft
If this command returns without error, our syntax is good. Now, we’ll load the new ruleset into a temporary table. This is where the magic begins. We’ll use the nft --atomic flag, which ensures that the entire operation within that command is treated as a single, indivisible unit.
nft --atomic -f new_rules.nft
This command, when executed, doesn’t immediately replace our existing table ip filter. Instead, it creates a new table with the same name (ip filter) but with the rules defined in new_rules.nft. Because we used --atomic, if there’s any issue during the creation of this new table and its rules, the entire operation is rolled back, leaving our original table ip filter untouched.
Once the new table is successfully created and populated with the new rules, we need to swap it with the old one. The nft swap command is designed for this. It atomically replaces one table with another.
nft swap table ip filter new_filter
Here, new_filter is the temporary table name that nft --atomic -f new_rules.nft implicitly created (or we could explicitly name it if we were using nft add table followed by nft add set etc. in a more complex sequence). If nft --atomic -f new_rules.nft succeeded, it created a table named ip filter that contains the new rules. We then tell nft swap to swap the existing table ip filter (the old one) with this newly created table ip filter (which is what nft --atomic -f new_rules.nft would have created if no table of that name existed, or it would have replaced it if it did).
Wait, that’s not quite right. The nft --atomic -f new_rules.nft command replaces the table ip filter if it already exists. The swap command is more for when you’re managing multiple distinct tables and want to switch which one is active.
The correct atomic approach for replacing a ruleset is simpler. You load the new ruleset, and if it’s valid, it replaces the old one in a single, atomic operation. The nft -f command itself, when applied to an existing table name, performs this atomic replacement.
Let’s re-trace with the correct atomic mechanism.
First, ensure your existing ruleset is loaded and active:
# Assume current_rules.nft is loaded
nft -f current_rules.nft
Now, prepare your new_rules.nft.
table ip filter {
chain input {
type filter hook input priority 0; policy accept;
# Allow SSH only from 192.168.1.100
tcp dport ssh ip saddr 192.168.1.100 accept
}
}
To atomically replace the entire table ip filter with the contents of new_rules.nft, you use nft -f with the new file. The --atomic flag is implicitly used by nft -f when it’s replacing an existing table.
nft -f new_rules.nft
This single command does the following:
- It parses
new_rules.nft. - If parsing is successful, it creates the new table structure defined in the file.
- It then atomically swaps the new table structure with the old
table ip filter. This means that at no point is thefiltertable partially updated. Either the old rules are active, or the new rules are active.
Let’s confirm the new rules are active:
nft list ruleset
You should see the new configuration, with only SSH from 192.168.1.100 allowed.
Common Causes for Failure and How to Fix Them:
-
Syntax Errors in the New Ruleset: The most common reason for
nft -f new_rules.nftto fail is a simple typo or structural mistake in the new file.- Diagnosis: Run
nft -c -f new_rules.nft. The-cflag (check) will parse the file and report any syntax errors without attempting to apply them. - Fix: Carefully review the error message from
nft -cand correct the syntax innew_rules.nft. For example, if you forgot atcp dportkeyword,nft -cmight showsyntax error, unexpected dport. - Why it works: This ensures the ruleset is syntactically valid before nftables attempts to load it, preventing partial application.
- Diagnosis: Run
-
Existing Table Name Conflicts (if not replacing): If
new_rules.nftdefines a table with the same name as an existing table, and you’re not intending to replace it atomically, you’ll get errors. However,nft -fdoes replace the table if it exists. The issue arises if you’re trying to add a table that already exists.- Diagnosis: If
nft -f new_rules.nftfails with a message like "table already exists," and you intended to replace it, this isn’t actually a failure of the atomic replacement. If you intended to add a new, distinct table, you’d need a different name. - Fix: For atomic replacement,
nft -f new_rules.nftis correct. If you need a separate table, rename the table innew_rules.nft(e.g.,table ip filter new_filter { ... }). - Why it works:
nft -fis designed to either create a new table or atomically replace an existing one with the same name.
- Diagnosis: If
-
Invalid Hook/Priority Combinations: nftables has strict rules about where chains can be hooked and their priorities.
- Diagnosis:
nft -c -f new_rules.nftwill report errors like "invalid hook 'prerouting' for chain type 'filter'" or "chain already exists with different type/hook/priority". - Fix: Ensure your
type,hook, andprioritydeclarations in thechaindefinition are correct for the intended traffic flow. For theinputchain,type filter hook input priority 0is standard. - Why it works: Correctly defining chain placement and type ensures nftables can integrate the new rules into the kernel’s networking stack without conflicts.
- Diagnosis:
-
Referencing Non-Existent Sets or Maps: If your new ruleset refers to sets or maps that haven’t been defined yet within the same
nft -foperation, it will fail.- Diagnosis:
nft -c -f new_rules.nftwill report errors like "set 'my_set' not found". - Fix: Ensure that any sets or maps referenced by rules are defined before the rules that use them within the same
nft -fcommand, or are defined in separate, precedingnft add setcommands. - Why it works: nftables needs to know the structure of all elements (tables, chains, sets, maps) before it can apply the rules, ensuring all dependencies are met atomically.
- Diagnosis:
-
Incorrect Address Family: Trying to apply IPv6 rules to an IPv4 table (or vice-versa) will cause issues.
- Diagnosis:
nft -c -f new_rules.nftmight complain about incompatible family types or specific rule syntax. For instance, an IPv6ip6 saddrrule in aniptable. - Fix: Ensure your
tabledeclaration matches the IP version of the addresses and protocols you’re using (e.g.,table ip6 ...for IPv6). - Why it works: This respects the fundamental separation of IPv4 and IPv6 routing and filtering paths within the kernel.
- Diagnosis:
-
Permissions Issues: The user running the
nftcommand might not have the necessary privileges to modify the kernel’s netfilter rules.- Diagnosis: The command will likely fail with a "permission denied" error.
- Fix: Run the
nftcommand usingsudoor as the root user. - Why it works: Modifying netfilter rules is a privileged operation.
After successfully applying nft -f new_rules.nft, your next potential issue might be that your new restrictive rule is blocking legitimate management traffic, leading to a loss of access if you’re not careful.