Skip to main content
RunBook Academy

Docker & ContainersXXVII · Firewallsnftables

nftables hosts — which ruleset is actually in charge

Expert⏱ ~22 min

What you'll learn

  • Distinguish iptables-nft from a native nftables firewall backend
  • Read Docker rules out of nft list ruleset
  • Recognise the legacy/nft split-ruleset failure
  • Place your own nftables base chain relative to Docker

Prerequisites

Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-11

Not yet marked complete on this device.

“This host uses nftables” describes two completely different situations, and they need different answers. Establish which one you are in before you write a rule.

Situation 1: nftables kernel, iptables tooling

This is the common case on every current distribution. iptables is a compatibility front end — iptables-nft — that translates your rules into nftables and loads them into a table named ip filter or ip nat. There is no legacy x_tables code in the path.

Docker’s rules go through the same front end. So on a modern “nftables host”, Docker’s chains are nftables rules that happen to be named DOCKER-USER, DOCKER-FORWARD and so on. Everything in docker-user-chain-and-rule-persistence applies unchanged.

Read-only / Safewhich front end
$ iptables --version
docker info --format 'Firewall Backend: {{.FirewallBackend.Driver}}'
iptables v1.8.11 (nf_tables)
Firewall Backend: iptables

You can read Docker’s rules with either tool. They are the same rules:

Read-only / Safethe same rules, two views
sudo iptables -S DOCKER-USER
sudo nft list chain ip filter DOCKER-USER
sudo nft list ruleset | head -60

The split-ruleset failure

iptables and iptables-legacy are two different binaries writing to two different subsystems. If Docker is using one and your tooling or a leftover boot script is using the other, you have two rulesets, each invisible to the other’s tooling. iptables -S shows your rules, the kernel enforces a different set, and every diagnosis you make from that output is wrong.

Read-only / Safedetect the split
iptables --version
sudo iptables-legacy -S 2>/dev/null | head
sudo iptables-nft -S | head
update-alternatives --display iptables 2>/dev/null || true
sudo lsmod | grep -E 'ip_tables|nf_tables'

If iptables-legacy -S returns anything beyond the three empty built-in chains, something is writing to the legacy path. Find it and migrate it. Running both is not a supported configuration and the symptom — “the rule is there and it does nothing” — costs hours.

Situation 2: Docker’s native nftables backend

Docker 29.0 added an nftables firewall backend. It is experimental; upstream says “configuration options, behavior and implementation may all change in future releases”. Treat it as something to know about rather than something to standardise on.

{
  "firewall-backend": "nftables"
}

or --firewall-backend=nftables on the daemon command line.

What changes:

  • Docker creates tables named ip docker-bridges and ip6 docker-bridges, with base chains plus per-network chains. The internal layout may change between releases, so do not build tooling that parses it.
  • There is no DOCKER-USER equivalent. The chain you have been told to use for the last decade does not exist here.
  • It cannot be enabled while the daemon is in Swarm mode; overlay network rules have not been migrated.
Read-only / Safeinspect the native backend
docker info --format '{{.FirewallBackend.Driver}}'
sudo nft list tables
sudo nft list table ip docker-bridges

Placing your own rules

Because there is no DOCKER-USER, you do not add rules to Docker’s tables — Docker owns them and will rewrite them. You create your own table with a base chain at the same hook, and use priority to decide whether it runs before or after Docker’s chain. Lower priority values run first.

The standard named priorities are the ones every nftables document uses:

NameValueHook typically used
raw-300prerouting
mangle-150all
dstnat-100prerouting
filter0forward, input, output
security50filter hooks
srcnat100postrouting

Do not guess the number Docker uses. Read it:

sudo nft -a list table ip docker-bridges | grep -E 'hook|priority'

Then give your chain a lower value if you need to be evaluated first. Upstream also documents --bridge-accept-fwmark, which lets a firewall mark set elsewhere override Docker’s drop rules — useful when your policy lives in a mangle chain earlier in the path.

Sanity check

Knowledge check · 4 questions

  1. Q1. On a host where iptables --version reports (nf_tables) and docker info reports the iptables firewall backend, where are Docker rules visible?

  2. Q2. A hand-written table inet filter has an input chain with policy drop. Why does it not block a published container port?

  3. Q3. Which statements about Docker 29 native nftables backend are correct? Select all that apply.

  4. Q4. If iptables-legacy -S and iptables-nft -S both return rules, one of the two rulesets is being silently ignored by whoever reads only the other.

Passing score: 75%. Answers are checked in this browser.