LFCE 78 ๐ง nftables โ Tables, Chains, Rules
The previous chapter introduced the nftables fundamentals: the nft command, the table and chain model, and the absence of predefined objects. This chapter examines the three levels of the model in detail: the table that groups the chains, the chain that is attached to a hook and contains the rules, and the rule that matches packets and applies verdicts. The syntax is different from iptablesโit is closer to a programming language than to a command-line toolโand the flexibility of the model is the reason nftables can replace four separate tools with one.
The table is the top-level container. It belongs to a family (ip, ip6, inet, arp, bridge, or netdev), and the family determines what kind of traffic the table can see. The chain belongs to a table, and it can be a base chain that is attached to a Netfilter hook, a regular chain that is jumped to from a rule, or a binding chain that is attached to a specific hook with a specific priority . The rule belongs to a chain, and it consists of expressions that match packet fields followed by statements that apply verdicts or actions. The expressions are evaluated from left to right, and the rule matches if all the expressions match.
Key point: A base chain must specify a type, a hook, and a priority. The type is filter, nat, or route, and it determines what the chain can do. The hook is prerouting, input, forward, output, or postrouting, and it determines where the chain is evaluated. The priority is an integer that determines the order among multiple chains on the same hook; lower values are evaluated first . A regular chain has no type, hook, or priority, and it is used as a subroutine that a rule jumps to with the jump statement. A binding chain is attached to a hook with the device parameter, which restricts it to a specific network interface .
Why the table, chain, and rule model requires explicit configuration
The family-selection problem.
The family determines the protocol scope of the table. The ip family sees only IPv4 traffic, the ip6 family sees only IPv6 traffic, and the inet family sees both. A table in the ip family does not see IPv6 packets, and a rule that is meant to block a port does not apply to IPv6 traffic unless a separate ip6 table exists . The inet family is the recommended choice for a host firewall because it eliminates the need to duplicate the rules for each protocol family.
The base-chain problem.
A chain that is not attached to a hook is a regular chain, and it never sees any packets. The chain must be a base chain with a type and a hook to be evaluated by the kernel. A common mistake is to create a chain with nft add chain inet filter input without the type and hook parameters, which creates a regular chain that is never invoked . The chain must be created with the full specification, or it must be converted to a base chain with the nft add chain command that includes the type and hook.
The priority problem.
Multiple base chains can be attached to the same hook, and the priority determines the order in which they are evaluated. The default priority is 0, and lower values are evaluated first. The nat chains are typically assigned a priority of 100, the filter chains a priority of 0, and the mangle chains a priority of -150 . A chain with the wrong priority may be evaluated after a chain that drops the packet, and its rules never take effect.
a. Creating tables and chains
The nft add table command creates a table, and the nft add chain command creates a chain inside it. The base chain specification includes the type, hook, and priority.
# Create a table in the inet family
sudo nft add table inet filter
# Create a base chain attached to the input hook
sudo nft add chain inet filter input { type filter hook input priority 0\; policy drop\; }
# Create a regular chain (no hook)
sudo nft add chain inet filter mychain
# Create a base chain attached to the forward hook
sudo nft add chain inet filter forward { type filter hook forward priority 0\; policy drop\; }
The add table inet filter command creates a table named filter in the inet family. The add chain inet filter input command creates a base chain named input, attached to the input hook, with the default priority of 0 and the policy drop . The add chain inet filter mychain command creates a regular chain with no hook, which is used as a subroutine. The add chain inet filter forward command creates a base chain for the forward hook.
The table and chain can also be defined declaratively in a configuration file.
# /etc/nftables.conf
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
The declarative form creates the table and the chains in one block. The table inet filter block contains the chain definitions, and each chain’s type, hook, priority, and policy are specified in the braces . This is the recommended form for a configuration file, because it is easier to read and to apply atomically.
b. Adding rules to chains
A rule is added with the nft add rule command, followed by the table, the chain, and the expressions and statements. The expressions are the match criteria, and the statements are the actions.
# Allow loopback
sudo nft add rule inet filter input iif lo accept
# Allow established and related connections
sudo nft add rule inet filter input ct state established,related accept
# Allow SSH from the local network
sudo nft add rule inet filter input ip saddr 192.168.1.0/24 tcp dport 22 accept
# Allow HTTP and HTTPS
sudo nft add rule inet filter input tcp dport { 80, 443 } accept
The iif lo expression matches packets that arrive on the loopback interface, and the accept statement accepts them. The ct state established,related expression matches packets that belong to an existing connection or a related connection, and the accept statement accepts them . The ip saddr 192.168.1.0/24 tcp dport 22 expression matches packets from the specified network destined for port 22, and the accept statement accepts them. The tcp dport { 80, 443 } expression uses an anonymous set to match either port, and the accept statement accepts them.
The nft insert rule command inserts a rule at the beginning of the chain, which is useful when the rule must be evaluated before the existing rules.
# Insert a rule at the top of the chain
sudo nft insert rule inet filter input tcp dport 22 accept
The insert command is the equivalent of iptables’ -I flag. The add command appends the rule to the end of the chain .
c. Custom chains and jumps
A custom chain is a regular chain that is jumped to from a rule. It is used to group related rules and to avoid repeating the same criteria.
# Create a custom chain
sudo nft add chain inet filter ssh_chain
# Add rules to the custom chain
sudo nft add rule inet filter ssh_chain ip saddr 192.168.1.0/24 accept
sudo nft add rule inet filter ssh_chain ip saddr 10.0.0.0/8 accept
# Jump to the custom chain from the input chain
sudo nft add rule inet filter input tcp dport 22 jump ssh_chain
The ssh_chain is a regular chain with no hook. The two rules in the chain accept SSH from the two allowed networks. The jump ssh_chain statement in the input chain transfers the packet to the custom chain, and the rules in the custom chain are evaluated . If a packet is accepted in the custom chain, it is accepted in the calling chain. If the custom chain returns without a verdict, the evaluation continues in the calling chain after the jump.
The goto statement is similar to jump, but it does not return to the calling chain. A packet that reaches the end of the goto chain is dropped if the chain is a base chain with a drop policy, or it continues at the next base chain if the chain is a regular chain .
# Jump vs goto
sudo nft add rule inet filter input tcp dport 22 jump ssh_chain
sudo nft add rule inet filter input tcp dport 80 goto web_chain
The jump statement returns to the calling chain when the custom chain finishes. The goto statement does not return; the packet’s fate is determined by the custom chain’s rules and the chain’s own policy .
Complete Example Session
# ============================================
# PART 1: CREATE THE TABLE
# ============================================
sudo nft add table inet filter
# ============================================
# PART 2: CREATE THE BASE CHAINS
# ============================================
sudo nft add chain inet filter input { type filter hook input priority 0\; policy drop\; }
sudo nft add chain inet filter forward { type filter hook forward priority 0\; policy drop\; }
sudo nft add chain inet filter output { type filter hook output priority 0\; policy accept\; }
# ============================================
# PART 3: CREATE A CUSTOM CHAIN
# ============================================
sudo nft add chain inet filter ssh_chain
# ============================================
# PART 4: ADD RULES TO THE CUSTOM CHAIN
# ============================================
sudo nft add rule inet filter ssh_chain ip saddr 192.168.1.0/24 accept
sudo nft add rule inet filter ssh_chain ip saddr 10.0.0.0/8 accept
# ============================================
# PART 5: ALLOW LOOPBACK
# ============================================
sudo nft add rule inet filter input iif lo accept
# ============================================
# PART 6: ALLOW ESTABLISHED AND RELATED
# ============================================
sudo nft add rule inet filter input ct state established,related accept
# ============================================
# PART 7: JUMP TO THE SSH CHAIN
# ============================================
sudo nft add rule inet filter input tcp dport 22 jump ssh_chain
# ============================================
# PART 8: ALLOW HTTP AND HTTPS
# ============================================
sudo nft add rule inet filter input tcp dport { 80, 443 } accept
# ============================================
# PART 9: VERIFY THE RULES
# ============================================
sudo nft list chain inet filter input
sudo nft list chain inet filter ssh_chain
# Output shows the rules in each chain
# ============================================
# PART 10: SAVE THE CONFIGURATION
# ============================================
sudo nft list ruleset > /etc/nftables.conf
The ten parts demonstrate the table, chain, and rule model: creating the table, the base chains, the custom chain, the custom-chain rules, the loopback rule, the established-connection rule, the jump, the HTTP/HTTPS rule, the verification, and the save. The critical distinction is between Parts 2 and 3: the base chains have a type and a hook, and the custom chain does not.
Quick Reference
Table Commands
| Command | Purpose |
|---|---|
nft add table <family> <name> | Create a table |
nft delete table <family> <name> | Delete a table |
nft list tables | List all tables |
nft flush table <family> <name> | Flush a table’s chains |
Chain Commands
| Command | Purpose |
|---|---|
nft add chain <family> <table> <chain> | Create a regular chain |
nft add chain <family> <table> <chain> { type ... hook ... priority ...\; } | Create a base chain |
nft delete chain <family> <table> <chain> | Delete a chain |
nft list chain <family> <table> <chain> | List a chain’s rules |
Rule Commands
| Command | Purpose |
|---|---|
nft add rule <family> <table> <chain> <expr> <stmt> | Append a rule |
nft insert rule <family> <table> <chain> <expr> <stmt> | Insert at the top |
nft delete rule <family> <table> <chain> handle <n> | Delete by handle |
nft list chain ... | Show the rules |
Chain Types and Hooks
| Type | Hooks | Priority |
|---|---|---|
filter | All five | 0 |
nat | prerouting, input, output, postrouting | 100 |
route | output | -150 |
Best Practices
โ Do This:
# Use the inet family for dual-stack
nft add table inet filter # โ
# Create base chains with type, hook, and priority
nft add chain inet filter input { type filter hook input priority 0\; policy drop\; } # โ
# Use custom chains for grouped rules
nft add chain inet filter ssh_chain # โ
# Jump to the custom chain
nft add rule inet filter input tcp dport 22 jump ssh_chain # โ
# Set the policy explicitly
policy drop # โ
# Save the configuration
nft list ruleset > /etc/nftables.conf # โ
โ Don’t Do This:
# Create a chain without a hook and expect it to see packets
nft add chain inet filter input # Regular chain, no packets # โ
# Forget the policy on a base chain
# The default policy is accept, which is insecure # โ
# Use goto when a return is expected
nft add rule ... goto ssh_chain # No return to caller # โ
# Apply a configuration file without flushing
# The rules are appended, not replaced # โ
# Mix ip and inet families carelessly
# The ip family does not see IPv6 traffic # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Chain sees no packets | Created without type and hook | Create a base chain |
| Policy not enforced | Policy not set | Add policy drop; |
| Rules appended to old config | No flush ruleset | Add flush ruleset |
| Custom chain not reached | No jump rule | Add the jump in the base chain |
| Jump does not return | Used goto instead of jump | Use jump |
| IPv6 not filtered | Table in ip family | Use inet family |
Real-World Examples
1. Create a table
nft add table inet filter
2. Create a base chain
nft add chain inet filter input { type filter hook input priority 0\; policy drop\; }
3. Create a regular chain
nft add chain inet filter mychain
4. Add a rule
nft add rule inet filter input tcp dport 22 accept
5. Insert a rule
nft insert rule inet filter input tcp dport 22 accept
6. Jump to a custom chain
nft add rule inet filter input tcp dport 22 jump ssh_chain
7. List a chain
nft list chain inet filter input
8. Delete a chain
nft delete chain inet filter mychain
9. Flush a table
nft flush table inet filter
10. Save the ruleset
nft list ruleset > /etc/nftables.conf
Visual
Table, Chain, and Rule Hierarchy
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TABLE โ CHAIN โ RULE โ
โ โ
โ table inet filter โ
โ โโโ chain input (base) โ
โ โ type filter hook input priority 0; policy drop; โ
โ โ โโโ rule: iif lo accept โ
โ โ โโโ rule: ct state established,related accept โ
โ โ โโโ rule: tcp dport 22 jump ssh_chain โ
โ โโโ chain forward (base) โ
โ โ type filter hook forward priority 0; policy drop; โ
โ โโโ chain output (base) โ
โ โ type filter hook output priority 0; policy accept; โ
โ โโโ chain ssh_chain (regular) โ
โ โโโ rule: ip saddr 192.168.1.0/24 accept โ
โ โโโ rule: ip saddr 10.0.0.0/8 accept โ
โ โ
โ The base chains are attached to hooks. โ
โ The regular chain is jumped to from a base chain. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
jump vs goto
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ JUMP RETURNS; GOTO DOES NOT โ
โ โ
โ jump ssh_chain: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ input chain: โ โ
โ โ rule 1 โ rule 2 โ jump ssh_chain โ rule 3 โ โ
โ โ โ โ
โ โ ssh_chain: โ โ
โ โ rule A โ rule B โ (return to input chain) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ goto web_chain: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ input chain: โ โ
โ โ rule 1 โ rule 2 โ goto web_chain โ (no return) โ โ
โ โ โ โ
โ โ web_chain: โ โ
โ โ rule A โ rule B โ (end of chain) โ โ
โ โ โ the packet's fate is determined by the chain โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Chain Priority
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LOWER PRIORITY NUMBERS ARE EVALUATED FIRST โ
โ โ
โ Hook: input โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ priority -150: mangle chain โ โ
โ โ priority 0: filter chain โ โ
โ โ priority 100: another filter chain โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The mangle chain is evaluated first, then the filter, โ
โ then the second filter. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Table families | ip, ip6, inet, arp, bridge, netdev |
| Base chain | Requires type, hook, priority |
| Regular chain | No hook; used as a subroutine |
| Chain types | filter, nat, route |
| Hooks | prerouting, input, forward, output, postrouting |
| Default priority | 0 |
| Lower priority | Evaluated first |
| Jump | Returns to the calling chain |
| Goto | Does not return |
| Rule | Expressions + statements |
| Configuration file | /etc/nftables.conf |
Key takeaways:
- The table is the top-level container, and the family determines its protocol scope. The
inetfamily sees both IPv4 and IPv6 traffic, and it is the recommended choice for a host firewall. Theipandip6families see only one protocol each . - A base chain must specify a type, a hook, and a priority. The type determines what the chain can do, the hook determines where it is evaluated, and the priority determines the order among multiple chains on the same hook. A regular chain has no hook and is used as a subroutine .
- The
jumpstatement returns to the calling chain; thegotostatement does not. A custom chain that is reached withjumpreturns control when its rules finish, and the evaluation continues in the calling chain. A chain that is reached withgotodetermines the packet’s fate without returning . - The rules are evaluated in order, and the first matching rule determines the packet’s fate. The expressions in a rule are combined with a logical AND, and the statements apply verdicts or actions. A single rule can have multiple statements.
- The configuration file uses a declarative syntax with
flush rulesetat the top. Theflush rulesetcommand clears the existing configuration, and thetableblock defines the new tables and chains. The file is applied withnft -fand restored at boot bynftables.service. - The
nft list rulesetcommand shows the entire configuration. It is the equivalent ofiptables-save, and its output can be redirected to/etc/nftables.confto save the current ruleset.
Remember: nftables is organized into tables, chains, and rules. The table groups the chains, the chain is attached to a hook with a type and a priority, and the rule matches packets and applies verdicts. The inet family handles dual-stack traffic, the base chain is the one that sees packets, and the regular chain is a subroutine that is jumped to with the jump statement. The configuration is stored in /etc/nftables.conf with a flush ruleset at the top, and it is restored at boot by the nftables service. The model is more flexible than iptables, and the syntax is closer to a programming language, but the concepts are the same: match the packet, apply the verdict, and order the rules so that the specific ones are evaluated first.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!