nftables syntax¶
Synopsis¶
nft [OPTIONS] COMMAND OBJECT [FAMILY] [TABLE [CHAIN]] [SPECIFICATION]
nft [OPTIONS] -f FILE
nft [OPTIONS] -i
ruleset ─┬─ table <family> <name>
│ ├─ chain <name> (base chain: type, hook, priority, policy)
│ │ └─ rule (expressions … statement)
│ ├─ chain <name> (regular chain: jump/goto target)
│ ├─ set <name>
│ ├─ map <name>
│ └─ stateful objects (counter, quota, limit, ct helper, …)
└─ table <family> <name> …
Description¶
nft is the userspace command of nftables, the packet classification framework of the Linux kernel (the kernel side is called nf_tables).
It parses commands or a ruleset file, compiles them into nf_tables bytecode, and sends them to the kernel over netlink.
Every change made by one nft invocation, including a whole file, is applied as a single atomic transaction.
Rules exist only in kernel memory: they are lost on reboot unless a service loads them from a file at boot.
nft needs the CAP_NET_ADMIN capability, so it is normally run as root or with sudo.
On Debian, nft is installed in /usr/sbin, which isn't in an unprivileged user's PATH.
Checked against nftables 1.1.3 on Debian 13, 1.1.6 on Ubuntu 26.04, 1.1.5 on AlmaLinux 10 and Rocky Linux 10, and 1.1.7 on Arch Linux and Gentoo (November 2026).
Address families¶
Every table belongs to one address family, which determines the packets it sees and the hooks its chains can use.
When a command omits the family, ip is assumed.
| Family | Packets | Hooks |
|---|---|---|
ip |
IPv4 | prerouting, input, forward, output, postrouting |
ip6 |
IPv6 | prerouting, input, forward, output, postrouting |
inet |
IPv4 and IPv6 | as ip, plus ingress (Linux 5.10 and later) |
arp |
ARP | input, output |
bridge |
Ethernet frames crossing a bridge | prerouting, input, forward, output, postrouting |
netdev |
Any packet on one interface | ingress, egress (Linux 5.16 and later) |
Tables¶
{add | create} table [FAMILY] NAME [{ [comment "TEXT";] [flags FLAGS;] }]
{delete | destroy | list | flush} table [FAMILY] NAME
list tables [FAMILY]
delete table [FAMILY] handle HANDLE
A table is a namespace for chains, sets, maps and stateful objects. Tables have no hook or priority of their own; only base chains do.
| Command | Effect |
|---|---|
add |
Create the table; no error if it already exists |
create |
Create the table; error if it already exists |
delete |
Delete the table and everything in it |
destroy |
As delete, but no error if it doesn't exist (nftables 1.0.7 and Linux 6.3 or later) |
flush |
Delete all rules from every chain in the table; chains, sets and maps remain |
list |
Print the table with its contents |
| Table flag | Effect |
|---|---|
dormant |
The table's base chains are unregistered and its rules aren't evaluated |
owner |
Only the creating process may change the table; it is removed when that process exits (nftables 0.9.9 or later) |
persist |
With owner: the table outlives its owner and can be taken over by a new process (nftables 1.1.0 or later) |
Chains¶
{add | create} chain [FAMILY] TABLE NAME [{ type TYPE hook HOOK [device DEV] priority PRIORITY; [policy POLICY;] [comment "TEXT";] }]
{delete | destroy | list | flush} chain [FAMILY] TABLE NAME
rename chain [FAMILY] TABLE NAME NEWNAME
list chains [FAMILY]
A base chain has type, hook and priority and is attached to a netfilter hook, so packets enter it.
A regular chain has none of these; packets reach it only by jump or goto from another chain.
add chain on an existing base chain with only { policy POLICY; } changes its policy.
On the command line, the braces and semicolons must be protected from the shell, for example by single quotes:
Chain types¶
| Type | Families | Hooks | Purpose |
|---|---|---|---|
filter |
all | all | Accept or drop packets; the general-purpose type |
nat |
ip, ip6, inet |
prerouting, input, output, postrouting |
Address translation; only the first packet of each connection traverses it |
route |
ip, ip6, inet |
output |
Re-route a locally generated packet if a rule changed fields that affect routing (marks, addresses) |
Base chains in the netdev family, and ingress chains in the inet family, also need device "NAME" (or devices = { "NAME", … }).
Priorities¶
priority is a signed integer or a standard name.
On each hook, chains run in ascending priority order, whatever table they are in; the order of chains with equal priority is undefined.
A name may be offset by addition or subtraction, for example filter - 5.
nat chains must have a priority greater than -200 (the priority of connection tracking).
| Name | Value | Families | Hooks |
|---|---|---|---|
raw |
-300 | ip, ip6, inet |
all |
mangle |
-150 | ip, ip6, inet |
all |
dstnat |
-100 | ip, ip6, inet |
prerouting |
filter |
0 | ip, ip6, inet, arp, netdev |
all |
security |
50 | ip, ip6, inet |
all |
srcnat |
100 | ip, ip6, inet |
postrouting |
The bridge family uses different values: dstnat -300 (prerouting), filter -200 (all), out 100 (output), srcnat 300 (postrouting).
Policies¶
| Policy | Effect on a packet that reaches the end of the base chain |
|---|---|
accept |
Accepted by this chain (the default). Later chains on the same hook still see it. |
drop |
Dropped |
Regular chains have no policy; at their end, evaluation returns to the calling chain.
Rules¶
{add | insert} rule [FAMILY] TABLE CHAIN [handle HANDLE | index INDEX] MATCH… STATEMENT… [comment "TEXT"]
replace rule [FAMILY] TABLE CHAIN handle HANDLE MATCH… STATEMENT… [comment "TEXT"]
{delete | reset} rule [FAMILY] TABLE CHAIN handle HANDLE
| Command | Effect |
|---|---|
add |
Append to the chain, or place after the rule given by handle or index |
insert |
Prepend to the chain, or place before the rule given by handle or index |
replace |
Replace the rule with this handle |
delete |
Delete the rule with this handle |
reset |
Reset stateful statements (counters, quotas) in the rule (nftables 1.0.7 or later) |
Each rule has a numeric handle, shown by nft -a list ….
Within a rule, matches are evaluated left to right and all must be true; the statements then run in order.
A rule with no match applies to every packet.
Rules in a chain are evaluated top to bottom until a verdict ends evaluation of the chain.
Operators and values¶
| Form | Example |
|---|---|
| Equality (implicit) | tcp dport 22 |
==, !=, <, >, <=, >= (or eq, ne, lt, gt, le, ge) |
tcp dport != 22, meta length > 1000 |
| Range | tcp dport 1024-65535 |
| Prefix | ip saddr 192.0.2.0/24 |
| Anonymous set | tcp dport { 80, 443 } |
| Named set | ip saddr @blocklist |
| Flags | tcp flags syn, ct state established,related |
Common matches¶
| Expression | Matches | Example values |
|---|---|---|
ip saddr, ip daddr |
IPv4 source or destination address | 192.0.2.10, 198.51.100.0/24 |
ip protocol |
IPv4 protocol field | tcp, udp, icmp |
ip6 saddr, ip6 daddr |
IPv6 source or destination address | 2001:db8::/32 |
ip6 nexthdr |
IPv6 next header field (the first extension header, if any) | tcp, ipv6-icmp |
tcp sport, tcp dport |
TCP source or destination port; implies TCP | 22, ssh, 8000-8999 |
tcp flags |
TCP flags | syn, syn,ack, fin, rst |
udp sport, udp dport |
UDP source or destination port; implies UDP | 53, 123 |
th sport, th dport |
Port of any transport protocol with ports at the usual offset | 53 |
icmp type |
ICMP message type | echo-request, destination-unreachable |
icmpv6 type |
ICMPv6 message type | echo-request, nd-neighbor-solicit, nd-router-advert |
ct state |
Connection tracking state | new, established, related, invalid, untracked |
ct status |
Connection tracking status bits | dnat, snat, assured |
ct mark |
Connection mark | 0x1 |
iif, oif |
Input or output interface, by index (resolved when the rule is loaded) | lo, "eth0" |
iifname, oifname |
Input or output interface, by name (compared per packet; supports * suffix) |
"eth0", "wg*" |
meta l4proto |
Transport protocol after any IPv6 extension headers | tcp, udp, icmp, ipv6-icmp |
meta nfproto |
Protocol family, in inet tables |
ipv4, ipv6 |
meta mark |
Packet mark | 0x1 |
meta pkttype |
Link-layer packet type | host, broadcast, multicast |
meta skuid, meta skgid |
Owner of the sending socket (output hook) |
0, www-data |
iif fails to load if the interface doesn't exist; iifname works for interfaces created later, at a small cost per packet.
Statements¶
Verdicts¶
| Statement | Effect |
|---|---|
accept |
Stop evaluating this chain and let the packet continue to the next base chain on the hook |
drop |
Discard the packet; no further chain sees it |
reject |
Discard the packet and send an error: ICMP port unreachable by default |
reject with tcp reset |
Answer TCP with a reset segment |
reject with icmp TYPE, reject with icmpv6 TYPE |
Answer with the given ICMP or ICMPv6 code (for example admin-prohibited) |
reject with icmpx TYPE |
Family-independent code for inet tables (port-unreachable, admin-prohibited, no-route, host-unreachable) |
jump CHAIN |
Evaluate the regular chain CHAIN, then return to the next rule here |
goto CHAIN |
Evaluate CHAIN and don't return |
return |
Return to the calling chain; at the end of a base chain, apply the policy |
continue |
Continue with the next rule (the implicit behaviour) |
queue |
Pass the packet to a userspace program through nfnetlink_queue |
reject is valid only in base chains on the prerouting, input, forward and output hooks, and in regular chains called only from them.
Other statements¶
| Statement | Syntax | Effect |
|---|---|---|
counter |
counter |
Count packets and bytes that reach it; shown as counter packets N bytes N |
log |
log [prefix "TEXT"] [level LEVEL] [flags FLAGS] |
Write packet details to the kernel log; log group N sends to nflog instead |
limit |
limit rate [over] N/UNIT [burst N packets] |
Match while under (or, with over, above) a rate; UNIT is second, minute, hour or day; byte rates use N kbytes/second |
ct |
ct mark set VALUE, ct helper set "NAME" |
Set connection tracking properties |
meta |
meta mark set VALUE |
Set packet metadata |
notrack |
notrack |
Exclude the packet from connection tracking (priority raw chains) |
snat, dnat |
snat to 203.0.113.1, dnat to 192.0.2.10:8080 |
Source or destination address translation (nat chains) |
masquerade |
masquerade |
Source NAT to the address of the outgoing interface |
redirect |
redirect to :8080 |
Destination NAT to the local host |
Statements are non-terminal unless they are verdicts, so counter, log and limit are usually combined with one: tcp dport 22 limit rate 10/minute counter accept.
Sets and maps¶
add set [FAMILY] TABLE NAME { type TYPE | typeof EXPRESSION; [flags FLAGS;] [timeout TIME;] [size N;] [elements = { … };] }
add map [FAMILY] TABLE NAME { type TYPE : TYPE | typeof EXPRESSION : EXPRESSION; [flags FLAGS;] [elements = { KEY : VALUE, … };] }
{add | delete | destroy} element [FAMILY] TABLE SET { ELEMENT, … }
{list | flush | delete} {set | map} [FAMILY] TABLE NAME
| Kind | Example | Notes |
|---|---|---|
| Anonymous set | tcp dport { 22, 80, 443 } accept |
Part of one rule; can't be changed without replacing the rule |
| Named set | ip saddr @blocklist drop |
Defined in the table; elements can be added or removed at any time |
| Map | dnat ip to tcp dport map { 80 : 192.0.2.10, 443 : 192.0.2.11 } |
Looks up a value from a key; in an inet table, NAT statements name the family (dnat ip, dnat ip6) |
| Verdict map | tcp dport vmap { 22 : accept, 23 : drop } |
Looks up a verdict from a key |
Common element types:
| Type | Values | Equivalent typeof |
|---|---|---|
ipv4_addr |
IPv4 addresses | ip saddr |
ipv6_addr |
IPv6 addresses | ip6 saddr |
inet_service |
Port numbers | tcp dport |
inet_proto |
Protocol numbers | meta l4proto |
ifname |
Interface names | iifname |
ether_addr |
MAC addresses | ether saddr |
mark |
Marks | meta mark |
TYPE . TYPE |
Concatenation, for example ipv4_addr . inet_service |
ip saddr . tcp dport |
| Set flag | Effect |
|---|---|
interval |
Elements may be ranges or prefixes |
timeout |
Elements can expire; a per-element timeout may be given |
dynamic |
The ruleset may add elements from the packet path (with add @set { … } or update @set { … }) |
constant |
Elements can't change after creation |
The nft command¶
Commands¶
| Command | Effect |
|---|---|
nft list ruleset [FAMILY] |
Print every table, chain, rule, set and map |
nft list tables, nft list chains, nft list sets |
Print objects of one kind |
nft list table FAMILY TABLE |
Print one table |
nft list chain FAMILY TABLE CHAIN |
Print one chain |
nft list set FAMILY TABLE SET |
Print one set with its elements |
nft list hooks [FAMILY] |
Print every function registered on each hook, with priorities |
nft flush ruleset [FAMILY] |
Delete all tables (and so everything else) |
nft add …, insert …, create …, replace …, delete …, destroy …, flush …, reset …, rename … |
Change objects, as described in the sections above |
nft monitor [new \| destroy] [OBJECTS] |
Print ruleset changes as they happen; nft monitor trace prints packets marked with meta nftrace set 1 |
nft describe EXPRESSION |
Print the data type and accepted values of an expression |
Options¶
| Option | Effect |
|---|---|
-f FILE, --file FILE |
Read commands or a ruleset from FILE (- for standard input) |
-c, --check |
Parse and validate, including in the kernel, without applying the changes |
-i, --interactive |
Read commands from an interactive prompt |
-D NAME=VALUE, --define NAME=VALUE |
Define a variable for the ruleset |
-I DIR, --includepath DIR |
Add a directory searched by include; the built-in default is /etc on Debian, Ubuntu, AlmaLinux, Rocky Linux and Gentoo, and /usr/share on Arch |
-o, --optimize |
Report and apply rule merges, for example rules that differ only in port |
-a, --handle |
Show handles in listings |
-s, --stateless |
Omit stateful information of rules and stateful objects, such as counter values, from listings |
-t, --terse |
Omit set elements from listings |
-n, --numeric |
Print addresses, ports and other values numerically |
-p, --numeric-protocol |
Print layer 4 protocols numerically |
-T, --numeric-time |
Print time values numerically |
-u, --guid |
Print UIDs and GIDs as names from /etc/passwd and /etc/group |
-y, --numeric-priority |
Print chain priorities as numbers |
-S, --service |
Translate ports to service names from /etc/services |
-N, --reversedns |
Translate addresses to host names |
-e, --echo |
Print objects as they are added, including their handles |
-j, --json |
Read or write the JSON format described in libnftables-json(5) |
-d LEVEL, --debug LEVEL |
Print debugging output (scanner, parser, eval, netlink, mnl, proto-ctx, segtree, all) |
-v, --version |
Print the version |
-V |
Print the version with build details |
Ruleset file¶
A ruleset file uses the same syntax as nft list ruleset prints, so a listing can be saved and loaded back.
Loading a file with nft -f is one transaction: if any line fails, none of it is applied.
#!/usr/sbin/nft -f
# Start from an empty ruleset, so reloading doesn't duplicate rules.
flush ruleset
define ssh_port = 22
define admins_v4 = { 192.0.2.0/24 }
define admins_v6 = { 2001:db8:10::/48 }
table inet filter {
set blocklist {
type ipv4_addr
flags interval
elements = { 198.51.100.0/24 }
}
chain input {
type filter hook input priority filter; policy drop;
ct state vmap { established : accept, related : accept, invalid : drop }
iif lo accept
ip saddr @blocklist counter drop
meta l4proto { icmp, ipv6-icmp } accept
tcp dport $ssh_port accept
ip saddr $admins_v4 tcp dport 8080 accept
ip6 saddr $admins_v6 tcp dport 8080 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
include "/etc/nftables.d/*.nft"
| Element | Meaning |
|---|---|
#!/usr/sbin/nft -f |
Lets the file be run as a script; ignored by nft -f |
# |
Starts a comment |
flush ruleset |
Removes the existing ruleset in the same transaction, so the file replaces it |
define NAME = VALUE |
A variable, used as $NAME |
include "PATH" |
Inserts another file; relative paths are searched in the include path; wildcards are allowed |
Newline or ; |
Separates statements and declarations |
table … { … } |
Block form of add table, add chain, add rule, add set |
A set holds elements of one type, so IPv4 and IPv6 addresses need separate sets, variables and rules, as with $admins_v4 and $admins_v6 above.
Files¶
| Path | Distribution | Purpose |
|---|---|---|
/etc/nftables.conf |
Debian, Ubuntu, Arch | Ruleset loaded by nftables.service |
/etc/sysconfig/nftables.conf |
RHEL, AlmaLinux, Rocky Linux | Ruleset loaded by nftables.service; by default it contains only a commented-out include of the examples in /etc/nftables/ |
/var/lib/nftables/rules-save |
Gentoo | Ruleset saved by rc-service nftables save and loaded by /etc/init.d/nftables (OpenRC), or saved by nftables-store.service and loaded by nftables-load.service (systemd profile) |
/etc/conf.d/nftables |
Gentoo (OpenRC) | Settings for the init script, such as the save file and whether to save on stop |
/etc/nftables/rules/main.nft |
Gentoo (systemd profile) | Ruleset loaded by Gentoo's nftables.service, which starts only if this file exists; an alternative to nftables-load.service and nftables-store.service |
/usr/share/doc/nftables/examples/ |
Debian, Ubuntu, Arch, AlmaLinux, Rocky Linux | Example rulesets |
/usr/share/doc/nftables-VERSION/examples/ |
Gentoo | Example rulesets |
/usr/share/nftables/ |
Arch, Gentoo | Example rulesets |
/etc/nftables/ |
RHEL, AlmaLinux, Rocky Linux | Example rulesets, which /etc/sysconfig/nftables.conf can include |
On Debian, Ubuntu and RHEL, nftables.service runs nft -f on its file when it starts or reloads, and flushes the whole ruleset when it stops.
On Arch, the unit only runs nft -f /etc/nftables.conf when it starts; it has no reload or stop action.
On RHEL and its rebuilds, firewalld is the default firewall and nftables.service is normally disabled; enabling both makes them overwrite each other's rules.
See also¶
nft(8),libnftables-json(5)(nftables 1.1.x)- nftables wiki
- nftables releases
- Netfilter architecture for hooks, priorities and connection tracking in context
Related¶
- Tutorial: Build your first nftables firewall
- How-to: How to open a port in the firewall
- Explanation: Netfilter architecture