Linux Packet Filtering with iptables and nftables

Last update: 2026-08-23 11:46:28 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

Linux packet filtering is implemented by Netfilter, a kernel framework that exposes hooks in the networking stack. iptables and nftables are not independent firewalls: they are userspace interfaces for programming packet classification and actions at those hooks.

iptables is the historical interface. Native nftables, configured with the nft command, replaces the separate iptables, ip6tables, arptables, and ebtables rule-management models with one language and one Netlink API. Compatibility tools can still accept iptables syntax while writing to the nftables kernel backend, so the command name alone does not identify the active implementation.

Netfilter Packet Flow #

Netfilter invokes registered operations as a packet moves through the network stack:

NetworkIningresspreroutingRoutffeoorrDwweaacrriddsiopNnoe+st-twi-ron-orp-uku-tt-iO>nugtLocalouPtrpouctess

The routing decision determines which path follows:

  • Packets addressed to a local socket traverse prerouting and input.
  • Packets routed through the host traverse prerouting, forward, and postrouting.
  • Packets created locally traverse output and postrouting.
  • An optional, device-specific ingress hook runs before prerouting.

The ingress hook can reject traffic before later networking work occurs, but IP fragments have not yet been reassembled there. A rule that reads a TCP or UDP header therefore cannot classify noninitial fragments at that point.

Hook names describe positions in packet processing, not built-in rules. nftables creates no chains by default; a ruleset must explicitly attach a base chain to each required hook.

Three Interfaces and Two Kernel APIs #

Current systems can expose three command interfaces backed by two different kernel rule APIs:

iixgpp_ettttaaa/bbbsllleeeetssss-olsAceyPkgnIoatpcatyxiippttaabblleess-nsfytntnNafex_ttlaibnlkesAPInnffttablessyntax
  • iptables-legacy programs the historical x_tables kernel API.
  • iptables-nft accepts iptables syntax but programs nf_tables through the compatibility layer and nft_compat.
  • nft uses the native nftables language and the nf_tables Netlink API.

The nftables backend compiles rules into instructions for an in-kernel, network-specific virtual machine. It reuses the existing Netfilter hook, connection-tracking, NAT, logging, queueing, and other supporting infrastructure.

Determine the selected iptables backend rather than inferring it from the binary name:

sh
iptables --version
readlink -f "$(command -v iptables)"

A version string containing (nf_tables) identifies the nftables backend. Distribution alternatives or symlinks can change the selection.

The iptables Model #

iptables organizes rules into predefined tables and built-in chains. The main IPv4 tables are:

TablePrimary Purpose
filterAccept or reject packets through INPUT, FORWARD, and OUTPUT.
natCreate address or port translations for new connections.
mangleModify packet fields and marks for specialized processing.
rawRun before connection tracking, including NOTRACK decisions.
securityApply security marks used by Linux security modules.

Rules in a chain are evaluated in order. Matches select packets by interface, address, protocol, port, connection state, mark, or an extension-specific property. A target then accepts, drops, rejects, logs, translates, or transfers control to another chain.

IPv4 and IPv6 use separate commands and rulesets:

sh
iptables -A INPUT -p tcp --dport 22 \
  -m conntrack --ctstate NEW -j ACCEPT

ip6tables -A INPUT -p tcp --dport 22 \
  -m conntrack --ctstate NEW -j ACCEPT

Incremental iptables-legacy changes require reading, modifying, and replacing a table. Concurrent writers therefore coordinate through an xtables lock. iptables-restore is preferable to a shell script containing many independent iptables -A commands because it replaces a complete table rather than exposing a sequence of partial policies.

The nftables Model #

nftables uses a hierarchy of families, tables, chains, rules, and stateful objects:

  1. A family selects the packet domain: ip, ip6, inet, arp, bridge, or netdev.
  2. A table is a namespace for chains, sets, maps, counters, and flowtables.
  3. A base chain attaches to a Netfilter hook and declares a chain type, priority, and optional default policy.
  4. A regular chain has no hook and is reached through jump or goto.
  5. A rule evaluates expressions from left to right and executes statements when the expressions match.

The inet family lets one ruleset process IPv4 and IPv6 traffic. Protocol- specific expressions still select one protocol family; for example, ip saddr matches IPv4 addresses and ip6 saddr matches IPv6 addresses.

A Dual-Stack Host Firewall #

The following file defines a stateful, default-deny input policy while allowing loopback traffic, replies, diagnostic ICMP, and SSH from an IPv4 administration network:

nft
#!/usr/sbin/nft -f

flush ruleset

table inet firewall {
    set admin_ipv4 {
        type ipv4_addr
        flags interval
        elements = { 192.0.2.0/24 }
    }

    chain input {
        type filter hook input priority filter
        policy drop

        ct state invalid drop
        ct state established,related accept

        iifname "lo" accept
        meta l4proto { icmp, ipv6-icmp } accept
        ip saddr @admin_ipv4 tcp dport 22 accept
    }

    chain forward {
        type filter hook forward priority filter
        policy drop
    }

    chain output {
        type filter hook output priority filter
        policy accept
    }
}

Validate and load a file as a single transaction:

sh
nft --check --file /etc/nftables.conf
nft --file /etc/nftables.conf
nft list ruleset

nft --file parses the complete batch and submits it as one kernel transaction. If any command fails, the transaction is not partially committed. Placing flush ruleset in the same file replaces the managed ruleset atomically. Omitting the flush while repeatedly loading an additive file can create duplicate rules.

Do not use flush ruleset when another service owns unrelated nftables tables. In that case, replace only the tables assigned to the current component.

Hooks, Priorities, and Verdicts #

A base chain declaration controls when it runs:

nft
type filter hook input priority filter; policy drop;
  • type filter selects ordinary packet filtering.
  • hook input attaches the chain to locally delivered traffic.
  • priority filter places it at the standard filtering priority, numerically zero.
  • policy drop applies when evaluation reaches the end without another verdict.

Several base chains can attach to the same hook. Netfilter evaluates them in ascending numerical priority, including internal operations such as defragmentation, connection tracking, destination NAT, and source NAT.

An accept verdict ends the current chain, but a later base chain at the same hook can still drop the packet. A drop verdict discards the packet immediately. Firewall analysis must therefore include every base chain at the hook, not only the first chain that returns accept.

Regular-chain control flow also differs:

  • jump service_checks enters another chain and returns when that chain reaches its end.
  • goto service_checks transfers control without returning to the calling chain.
  • return resumes the calling chain or applies the base-chain policy when returning from a base chain.

Connection Tracking #

The nf_conntrack subsystem associates packets with flows. Both iptables and nftables use it for stateful filtering and NAT. Common states are:

StateMeaning
newA valid packet starts or belongs to a flow that has not exchanged traffic in both directions.
establishedConntrack has observed valid traffic in both directions.
relatedA new flow is associated with an existing tracked connection.
invalidThe packet cannot be associated with a valid tracked flow.
untrackedEarlier policy explicitly excluded the packet from tracking.

Stateful rules admit reply traffic without opening every ephemeral destination port:

nft
ct state established,related accept
ct state invalid drop

Conntrack consumes finite kernel memory. If its table reaches nf_conntrack_max, the kernel cannot track additional flows and can drop new traffic. Monitor both values:

sh
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max

Raising the maximum without calculating memory use only moves the resource limit. High-flow systems also require timeout review, traffic validation, and capacity monitoring.

Network Address Translation #

Stateful NAT creates a translation binding from the first packet of a tracked flow. Later packets use conntrack state rather than reevaluating every NAT rule. Filtering therefore belongs in filter chains, not NAT chains.

A router can define destination and source NAT as follows:

nft
table inet nat {
    chain prerouting {
        type nat hook prerouting priority dstnat
        tcp dport 443 dnat ip to 192.0.2.20:8443
    }

    chain postrouting {
        type nat hook postrouting priority srcnat
        oifname "wan0" masquerade
    }
}

NAT in the inet family requires Linux 5.2 or later. Older kernels require separate ip and ip6 NAT tables.

  • DNAT commonly runs at prerouting, before the forwarding route is selected.
  • SNAT and masquerading commonly run at postrouting, after route selection.
  • redirect sends traffic to the local machine.
  • masquerade derives the source address from the output interface and is appropriate when that address can change.

NAT is not a security policy. A translated packet must still pass the relevant input or forward filtering policy.

Sets, Maps, and Concatenations #

iptables rulesets often express repeated values as linear rules or depend on the separate ipset facility. nftables integrates typed data structures:

nft
set web_ports {
    type inet_service
    elements = { 80, 443 }
}

map service_targets {
    type inet_service : ipv4_addr
    elements = {
        80 : 192.0.2.10,
        443 : 192.0.2.20
    }
}
  • A set answers whether a key is present.
  • A map returns a value for a key.
  • A verdict map returns a verdict such as accept, drop, or jump chain.
  • A concatenation combines fields, such as source address and destination port, into one compound key.

Named sets can be updated without rebuilding every rule that references them. Interval sets represent address and port ranges compactly. Maps and concatenations reduce long sequences of nearly identical rules because lookup selects data or control flow directly.

Counters, Logging, and Tracing #

Rules count traffic only when they contain a counter statement or reference a named counter:

nft
tcp dport 22 counter accept

Logging is a nonterminal statement, so one rule can both log and decide:

nft
ct state invalid log prefix "invalid: " limit rate 10/second drop

Rate limiting is necessary because packet-triggered logging can consume CPU, storage, and logging-service capacity.

nftables tracing records rule traversal for selected packets. Enable tracing with a narrowly scoped rule:

nft
meta l4proto tcp tcp dport 443 meta nftrace set 1

Then observe events:

sh
nft monitor trace

Remove the trace rule after diagnosis because broad tracing can generate a large event stream.

Migrating from iptables #

Migration has two distinct targets:

  1. Compatibility migration changes the kernel backend while retaining iptables syntax. iptables-nft-restore loads an iptables-save file into nf_tables.
  2. Native migration converts and then redesigns the policy in nftables syntax.

Inventory all active rule owners before changing either backend:

sh
iptables --version
iptables-legacy-save
ip6tables-legacy-save
nft list ruleset

The translation tools do not alter the live ruleset:

sh
iptables-legacy-save > iptables-v4.save
ip6tables-legacy-save > iptables-v6.save

iptables-restore-translate -f iptables-v4.save > rules-v4.nft
ip6tables-restore-translate -f iptables-v6.save > rules-v6.nft

Translation is a starting point because it preserves the old structure. Review the output for:

  • unsupported or partially supported xtables extensions;
  • duplicated IPv4 and IPv6 rules that can become one inet rule;
  • repeated rules that should become sets, maps, or verdict maps;
  • chain policies, counters, logging rates, and NAT behavior;
  • dependencies on marks, routing policy, interfaces, namespaces, or connection-tracking helpers; and
  • rules inserted dynamically by container engines, virtual-machine managers, intrusion-prevention tools, or firewall frontends.

iptables-nft is useful as a transition interface because its changes are atomic and visible through nft list ruleset. It still represents xtables semantics, and some extensions can remain wrapped through nft_compat rather than becoming native nftables expressions.

Coexistence and Rule Ownership #

Do not operate iptables-legacy and nftables as independent administrators of the same traffic. The legacy x_tables rules and nf_tables rules are separate kernel rulesets, and neither command view gives a complete policy. A packet can traverse both, producing failures that resemble missing or contradictory rules.

iptables-nft and nft share the nf_tables backend, but shared storage does not imply shared ownership. Editing compatibility-generated chains with native commands can violate assumptions made by the tool that created them.

Assign ownership explicitly:

  • Use one service to load the host firewall.
  • Give orchestration systems separate tables when they support that model.
  • Determine whether firewalld, container engines, Kubernetes components, virtual-machine managers, VPN software, and ban tools add rules.
  • Do not flush tables owned by another component.
  • Persist rules through the distribution’s nftables service or the selected firewall manager, not through competing boot scripts.

Backend support varies by software version and distribution packaging. Inspect the deployed system instead of assuming that a tool name implies legacy or native nftables behavior.

Project Status #

nftables has been available in the upstream Linux kernel since version 3.13. The Netfilter project has considered the original xtables/setsockopt tools legacy since June 2018, while continuing to provide iptables-compatible tools over the nftables backend.

Current iptables packages therefore serve two roles: maintaining existing legacy deployments and providing a compatibility path to nf_tables. Kernel, nftables, and iptables versions remain distribution-specific; verify the deployed backend and required feature versions instead of relying on a distribution release name.

Operational Checklist #

  1. Keep an out-of-band console available before changing a remote host’s input policy.
  2. Validate files with nft --check --file before applying them.
  3. Load complete changes with nft --file instead of issuing an ordered shell sequence.
  4. Establish loopback and management access before enforcing a default-drop policy.
  5. Review all base chains and priorities at each hook.
  6. Monitor counters, logs, conntrack capacity, and ruleset changes.
  7. Treat NAT, packet filtering, routing, and traffic control as separate policies because one does not replace the others.
  8. Keep one authoritative owner for each table and document dynamically managed tables.

For new Linux firewall designs, native nftables provides the clearest model, dual-stack inet rules, integrated sets and maps, and transactional updates. iptables compatibility remains valuable for existing applications, but it should be treated as a migration mechanism rather than the architecture for a new ruleset.

References #

Comments

Read or comment on GitHub (sign-in required to comment)