Chris Collins

Tools · network

Firewall packet tracer (iptables and nftables)

Paste iptables-save or nft list ruleset output and a packet, and step it through the netfilter hooks in the kernel's order — every chain, every rule tested with the reason it matched or didn't, jumps, returns, policies, DNAT and MASQUERADE — to the final verdict; with Docker's and kube-proxy's chains explained.

Added

How to use it

When a port forward doesn’t work or a container can’t be reached, the answer is in the ruleset, but iptables-save on a Docker or Kubernetes host is hundreds of lines across several tables, and nftables can add more tables on the same hooks. This page walks one packet through all of it in the order the kernel does and stops where the kernel would.

How to use it

  1. Paste the output of sudo iptables-save (legacy or nf_tables backend; add -c to keep the counters, ip6tables-save for IPv6, and iptables -S works too) and/or sudo nft list ruleset. Paste both if the host has both — the kernel runs every table on a hook. Or start from an example: a small router, the same router in native nftables, Docker 29 with its iptables and its nftables backends, and a kind node’s kube-proxy in iptables mode and in nftables mode.

  2. Describe the packet: incoming to this host, forwarded through it, or locally generated; the protocol, addresses and ports, the interfaces, and the conntrack state (NEW for the first packet of a connection, ESTABLISHED for the rest). Under This host, list the host’s own addresses (for INPUT vs FORWARD and addrtype/fib … type local matches) and a few routes (10.0.0.0/8 dev eth1, default dev eth0) so the out-interface is right, including after DNAT.

  3. Read the result: the verdict and the rule that decided it, the address translations, and the path — each hook with its base chains in priority order. Below it, every chain the packet entered lists every rule it was tested against: ✓ with the actions it took, or ✕ with the first condition that failed and why (destination port 22 is not 80).

It follows the kernel’s rules: on each hook the base chains run by priority (raw −300, conntrack −200, mangle −150, DNAT −100, filter 0, security 50, SNAT 100; nftables chains slot in by their own priorities, and for equal priorities the chain registered last runs first); accept ends only the current base chain, so a later chain on the same hook can still drop the packet; drop, reject and queue are final; NAT chains see only a connection’s first packet, and once one NAT chain has translated it the remaining NAT chains on that hook are skipped; a jump returns, a goto doesn’t; a base chain’s policy applies when its rules run out. Routing happens after PREROUTING (a DNAT can turn an incoming packet into a forwarded one) and again after OUTPUT.

Load balancing is random per connection: kube-proxy’s -m statistic --mode random --probability rules and nftables numgen random mod n maps are listed under Random choices, where you can follow another endpoint; each KUBE-SVC chain shows each endpoint’s share.

Well-known chains get a note: DOCKER-USER (where your own rules for published ports go — the packet is already DNATed there), DOCKER-FORWARD, DOCKER-CT, DOCKER-BRIDGE, DOCKER and its raw direct-access drop; KUBE-SERVICES, KUBE-SVC-, KUBE-SEP-, KUBE-EXT-, KUBE-NODEPORTS, KUBE-MARK-MASQ and KUBE-POSTROUTING; kube-proxy’s and Docker’s nftables tables; and the chains of Calico, Cilium, Flannel, ufw, fail2ban and libvirt.

Related tools: the container simulator shows how Docker’s bridge, veths and NAT fit together; the subnet calculator for CIDRs; the packet capture viewer to see what actually crossed the wire; and server config for the services behind the ports. For Kubernetes NetworkPolicies — which network plugins turn into rules like these — use the NetworkPolicy checker.

How faithful it is

The traces were checked against the kernel. In a throwaway privileged Debian container I set up a router between two network namespaces, loaded the example ruleset, sent one packet per case and read back which rules the kernel counted (iptables-save -c), with both the nf_tables and the legacy backend; the tracer matches exactly those rules in all 13 cases, and the NATed addresses match what tcpdump saw on the far side. The native nftables example was traced with nft monitor trace, and the page lists the same rules and policies in the same order. The Docker examples come from docker:29-dind (iptables and --firewall-backend=nftables) and were checked against their counters after a real connection to a published port; the kube-proxy examples come from a kind cluster and were checked against nft monitor trace for a ClusterIP connection from the node and a NodePort connection from outside.

What it doesn’t model: matches whose result depends on earlier traffic or state outside the ruleset — rate limits (assumed under the limit), recent, connlimit, quota, ipsets (their contents aren’t in iptables-save), socket, ct label, physdev, cgroup and BPF matches. Those are marked "?" and treated as matching unless you choose otherwise. It doesn’t do policy routing (ip rule), reverse-path filtering, bridge-family or netdev (ingress) chains, IPVS, or eBPF programs such as Cilium’s, which don’t appear in iptables or nft output at all. iptables -L output leaves out too much; use iptables-save.

Browser support

Any current browser.

Privacy

Everything runs in this page; nothing you paste is sent anywhere and the page makes no network requests. The ruleset and packet are kept in this browser’s local storage. Rulesets contain addresses and interface names — think before sharing a link, because a share link carries them.

Runs entirely in your browser; nothing you type leaves this page.