# nftables: Basic Rule Set

LLMS index: [llms.txt](/en/llms.txt)

---

> [!NOTE]
> All commands were verified on Debian/Ubuntu with the `nftables` package and on RHEL/CentOS 8+. On older systems you may need `apt install nftables` or `yum install nftables`.

nftables replaced iptables, but documentation for a basic rule set is often scattered. Here is the reference I use when bringing up a firewall on a new host.

## Creating the inet filter table

The `inet` family table unifies IPv4 and IPv6 under a single namespace. This is the preferred approach when both stacks are active on the host.

```bash
nft add table inet filter
```

If the table already exists the command returns an error. To avoid duplication when a script is re-run:

```bash
nft 'add table inet filter' 2>/dev/null || true
```

To wipe the current rule set before loading your own:

```bash
nft flush ruleset
```

> [!WARNING]
> `flush ruleset` removes **all** rules instantly. On a production machine run this only from the console, not over a remote session without a fallback.

## Input and forward chains

Chains bind to a table and define the interception point for traffic. A basic firewall needs `input` (traffic destined for the host itself) and `forward` (traffic passing through the host).

```bash
nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; }
nft add chain inet filter forward { type filter hook forward priority 0 \; policy drop \; }
```

Key components inside the curly braces:

| Component | Value |
|---|---|
| `type filter` | Chain type, standard for packet filtering |
| `hook input` / `hook forward` | Interception point in the network stack |
| `priority 0` | Processing priority |
| `policy drop` | Default policy — drop unmatched packets |

The syntax requires escaping semicolons inside the string or using single quotes as shown above.

> [!TIP]
> If you need to allow established connections, add an `output` chain with policy `accept` or use connection tracking in your `input` rules.

## Basic rules for input

With a chain set to `policy drop`, you must explicitly permit the traffic you need. A typical minimum:

```bash
# Allow loopback
nft add rule inet filter input iif lo accept

# Allow established and related connections
nft add rule inet filter input ct state established,related accept

# Allow SSH (port 22)
nft add rule inet filter input tcp dport 22 accept

# Allow ping (ICMP echo request)
nft add rule inet filter input ip protocol icmp icmp type echo-request accept
nft add rule inet filter input ip6 nexthdr icmpv6 icmpv6 type echo-request accept
```

Each rule appends to the end of the chain. Order matters: `accept` rules for loopback and established traffic should come before rules with narrower criteria.

To log dropped packets before the `drop` policy (optional but useful for diagnostics):

```bash
nft add rule inet filter input log prefix "nft-drop: " level warn
```

> [!NOTE]
> Logging adds overhead. On high-throughput interfaces use a rate limit: `limit rate 10/second`.

## Basic rules for forward

The `forward` chain is needed when the host acts as a router or NAT gateway. A minimum setup:

```bash
# Allow established connections
nft add rule inet filter forward ct state established,related accept

# Allow forwarding between specific interfaces (example)
nft add rule inet filter forward iifname "eth0" oifname "eth1" accept
```

If the host does not perform routing, leave `forward` with `policy drop` and no additional rules.

To enable IP forwarding at the kernel level (if not already done):

```bash
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1
```

## Viewing and managing rules

After setup, verify the current configuration:

```bash
nft list table inet filter
nft list chain inet filter input
nft list ruleset
```

`list ruleset` outputs the full config, which you can save and reuse as a boot script.

Deleting a specific rule by handle within a chain:

```bash
nft delete rule inet filter input handle <handle-number>
```

The `handle` number appears in the output of `nft list ruleset -a`.

To delete an entire chain:

```bash
nft delete chain inet filter input
```

A chain can only be deleted when it is empty. To remove a table completely, delete all chains inside it first.

Saving rules to a file for boot-time loading:

```bash
nft list ruleset > /etc/nftables.conf
```

On systems with systemd, enable auto-start:

```bash
systemctl enable nftables
systemctl start nftables
```

> [!WARNING]
> If `/etc/nftables.conf` does not exist or is empty, the service will not load any rules. Create the file manually before enabling the service.
