| |

LFCE 78 ๐Ÿง nftables โ€” Tables, Chains, Rules

โฑ๏ธ Estimated read: 15 min | |

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

CommandPurpose
nft add table <family> <name>Create a table
nft delete table <family> <name>Delete a table
nft list tablesList all tables
nft flush table <family> <name>Flush a table’s chains

Chain Commands

CommandPurpose
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

CommandPurpose
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

TypeHooksPriority
filterAll five0
natprerouting, input, output, postrouting100
routeoutput-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

PitfallWhy It HappensFix
Chain sees no packetsCreated without type and hookCreate a base chain
Policy not enforcedPolicy not setAdd policy drop;
Rules appended to old configNo flush rulesetAdd flush ruleset
Custom chain not reachedNo jump ruleAdd the jump in the base chain
Jump does not returnUsed goto instead of jumpUse jump
IPv6 not filteredTable in ip familyUse 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

ItemValue
Table familiesip, ip6, inet, arp, bridge, netdev
Base chainRequires type, hook, priority
Regular chainNo hook; used as a subroutine
Chain typesfilter, nat, route
Hooksprerouting, input, forward, output, postrouting
Default priority0
Lower priorityEvaluated first
JumpReturns to the calling chain
GotoDoes not return
RuleExpressions + statements
Configuration file/etc/nftables.conf

Key takeaways:

  • The table is the top-level container, and the family determines its protocol scope. The inet family sees both IPv4 and IPv6 traffic, and it is the recommended choice for a host firewall. The ip and ip6 families 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 jump statement returns to the calling chain; the goto statement does not. A custom chain that is reached with jump returns control when its rules finish, and the evaluation continues in the calling chain. A chain that is reached with goto determines 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 ruleset at the top. The flush ruleset command clears the existing configuration, and the table block defines the new tables and chains. The file is applied with nft -f and restored at boot by nftables.service .
  • The nft list ruleset command shows the entire configuration. It is the equivalent of iptables-save, and its output can be redirected to /etc/nftables.conf to 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.


Was this helpful?


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!