Skip to content

Stateful Linux Packet Filtering with nftables & UFW: Netfilter Hooks & Connection Tracking

Created: 2026-11-08  ·  Last updated: 2026-11-08  ·  Nejat Hakan

Executive Summary: Beneath every Linux firewall lies the Netfilter kernel subsystem. For over two decades, administrators struggled with legacy iptables, ip6tables, and arptables—monolithic, separate utilities that parsed rules sequentially with high performance penalties. In modern Linux distributions (Debian 11/12+), nftables provides a unified bytecode virtual machine inside the kernel. It delivers atomic ruleset replacement, dual-stack inet (IPv4 + IPv6) filtering, constant-time (O(1)) set lookups, and deep stateful connection tracking (conntrack).

  • Netfilter Kernel Hooks


    Trace the 5 fundamental hook locations: PREROUTING, INPUT, FORWARD, OUTPUT, and POSTROUTING inside the kernel packet pipeline.

  • nftables Architecture


    Understand the unified inet family, base chains, verdict maps, dynamic sets for rate-limiting, and atomic transactions.

  • Stateful conntrack


    Filter traffic based on packet relationship: NEW, ESTABLISHED, RELATED, and INVALID states to protect internal networks.

  • UFW for Edge Nodes


    Manage perimeter access rapidly using Uncomplicated Firewall (ufw) default policies, application profiles, and interface-specific rules.


1. The Netfilter Subsystem: The 5 Kernel Packet Hooks

The Linux kernel processes all inbound, routed, and outbound packets through specific interception points known as Netfilter Hooks:

%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
    NIC_IN["Ingress Packet (NIC RX Ring)"] --> PRE["1. PREROUTING Hook<br/>(DNAT / Port Redirection / Raw Mangling)"]
    PRE --> DEC{"Routing Decision<br/>Is destination IP local or remote?"}
    DEC -->|"Local IP"| INP["2. INPUT Hook<br/>(Host Firewall: Protects Local Daemons)"]
    DEC -->|"Remote IP"| FWD["3. FORWARD Hook<br/>(Router / VM / Container Forwarding)"]
    INP --> APP["Local Application Socket (e.g., Nginx, SSHd)"]
    APP --> OUT["4. OUTPUT Hook<br/>(Local Outbound Traffic)"]
    OUT --> ROUTE_OUT["Reroute Check"]
    ROUTE_OUT --> POST["5. POSTROUTING Hook<br/>(SNAT / Masquerade before wire)"]
    FWD --> POST
    POST --> NIC_OUT["Egress Packet (NIC TX Ring)"]

The 5 Netfilter Hooks Explained

  1. PREROUTING: Intercepts packets immediately after they leave the network interface driver, before any routing decision is made. Used for Destination NAT (DNAT) and port forwarding.
  2. INPUT: Executed for packets destined for local user-space sockets on the machine. This is where standard host firewalls block unauthorized inbound traffic.
  3. FORWARD: Executed for packets whose destination IP address belongs to another machine. Used when the Linux host acts as a router, NAT gateway, or container hypervisor.
  4. OUTPUT: Intercepts packets originated locally by applications before they traverse the kernel network stack toward the physical wire.
  5. POSTROUTING: Executed after all routing decisions are final, just before the packet is placed into the network interface TX ring buffer. Used for Source NAT (SNAT) and IP masquerading.

2. Why nftables Supersedes iptables

Feature Legacy iptables Modern nftables
Protocol Families 4 separate tools (iptables, ip6tables, arptables, ebtables). Unified inet family handles both IPv4 and IPv6 simultaneously in a single rule.
Rule Matching Speed Linear scan: checks rules sequentially (O(n) latency). Set lookups in constant time (O(1)) via internal hashes and trees.
Ruleset Updates Non-atomic: updating one rule requires re-evaluating or rewriting entire chains. Strictly atomic transactions: Entire /etc/nftables.conf loads instantaneously in RAM.
Execution Model Monolithic C code inside kernel modules for every protocol match. Lightweight kernel bytecode virtual machine driven by user-space syntax.
Port Filtering Multiple lines or slow multiport modules. Native comma-separated sets: tcp dport { 80, 443, 8080 } accept.

3. Stateful Connection Tracking (conntrack)

A stateless packet filter evaluates each packet in isolation, requiring administrators to open return ports manually. In contrast, stateful filtering tracks TCP/UDP/ICMP sessions in kernel RAM (conntrack table):

%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
    PKT["Incoming Packet"] --> CT["conntrack Table Lookup"]
    CT --> STATE{"Connection State?"}
    STATE -->|"ESTABLISHED / RELATED"| ACC["Instant ACCEPT<br/>(Part of valid existing session or FTP/ICMP error helper)"]
    STATE -->|"INVALID"| DRP["Instant DROP<br/>(Out-of-window TCP packets, malformed headers)"]
    STATE -->|"NEW"| RULE["Evaluate Policy Rules<br/>(Check port 22, 443, rate limits)"]

The 4 Core Connection States

  • NEW: The packet initiates a new connection (e.g., a TCP SYN packet).
  • ESTABLISHED: The packet belongs to a bidirectional connection that has already seen packets in both directions (e.g., completed 3-way handshake).
  • RELATED: The packet initiates a new connection associated with an existing connection (e.g., an ICMP Destination Unreachable response or FTP data channel).
  • INVALID: The packet cannot be identified or violates protocol state machine rules (e.g., unexpected FIN/RST flags). Must always be dropped immediately.

4. Production /etc/nftables.conf Ruleset

Here is a hardened, production-ready /etc/nftables.conf designed for Debian servers. It filters IPv4 and IPv6 in a single unified table, enforces stateful inspection, mitigates SSH brute-force attacks via dynamic rate-limited sets, and permits essential ICMP:

#!/usr/sbin/nft -f
# File: /etc/nftables.conf

# Flush all existing tables and rules atomically
flush ruleset

table inet filter {
    # Dynamic set to track and rate-limit SSH brute-force attempts
    set ssh_meter {
        type ipv4_addr
        flags dynamic, timeout
        timeout 1m
    }

    chain input {
        type filter hook input priority filter; policy drop;

        # 1. Accept established and related connections (Maximum performance)
        ct state established,related accept

        # 2. Drop malformed or out-of-order packets immediately
        ct state invalid drop

        # 3. Allow all traffic on the local loopback interface
        iifname "lo" accept

        # 4. ICMP & ICMPv6 Handling (MANDATORY for IPv6 Path MTU & NDP)
        ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 10/second accept
        ip6 nexthdr icmpv6 icmpv6 type {
            destination-unreachable, packet-too-big, time-exceeded,
            parameter-problem, echo-request, nd-router-advert,
            nd-neighbor-solicit, nd-neighbor-advert
        } accept

        # 5. OpenSSH (Port 22) with automated brute-force rate-limiting
        # Permits a maximum of 6 new connection attempts per minute per source IP
        tcp dport 22 ct state new add @ssh_meter { ip saddr limit rate 6/minute } accept

        # 6. Public Web Services (HTTP & HTTPS)
        tcp dport { 80, 443 } accept

        # 7. Explicit counter for all silently dropped packets
        counter drop
    }

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

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

Validating and Enabling nftables on Debian

# Check syntax without modifying active kernel rules
sudo nft -c -f /etc/nftables.conf

# Apply ruleset atomically
sudo nft -f /etc/nftables.conf

# Enable systemd service to persist across reboots
sudo systemctl enable --now nftables

5. Uncomplicated Firewall (UFW) for Edge Nodes

For single-node workstations, edge servers, or developers who prefer a streamlined CLI over raw nftables syntax, UFW acts as an intuitive front-end.

# Install UFW on Debian
sudo apt update && sudo apt install -y ufw

# 1. Enforce strict default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# 2. Add rate-limited SSH rule (drops IP after 6 connections in 30 seconds)
sudo ufw limit 22/tcp comment 'Rate-limited OpenSSH'

# 3. Open Web Ports
sudo ufw allow 80/tcp comment 'HTTP ACME'
sudo ufw allow 443/tcp comment 'HTTPS Reverse Proxy'

# 4. Restrict internal database access strictly to a trusted management subnet
sudo ufw allow in on eth0 from 10.10.20.0/24 to any port 5432 proto tcp comment 'Internal Postgres'

# 5. Enable the firewall
sudo ufw enable

Inspecting and Managing UFW

# View active rules with line numbers (essential for deletions)
sudo ufw status numbered

# Delete a rule by its number
sudo ufw delete 4

# View detailed status and logging levels
sudo ufw status verbose

6. Live Firewall Debugging & Monitoring

1. Real-Time Packet Drop Monitoring

Monitor kernel packet drops live as they occur:

# Stream live Netfilter events
sudo nft monitor

2. Inspecting the Connection Tracking Table

Inspect active tracked sessions in kernel RAM:

sudo apt install -y conntrack
# Dump active TCP connections
sudo conntrack -L -p tcp

7. Next Steps

Now that your host perimeter is fortified with stateful packet filters, connect remote servers and branch offices securely with a modern in-kernel cryptographic tunnel:

  • WireGuard: Kernel-VPN & Cryptokey Routing


    Deploy lightweight, high-throughput VPN tunnels using wireguard.ko, Noise protocol key exchange, and AllowedIPs routing.

    WireGuard VPN Guide