Side reading
Reading iptables output
Enough vocabulary to look under your firewall and understand what you are seeing, without becoming a netfilter expert.
You will not write raw firewall rules very often. You will read them, usually when something is being blocked and the friendly tool insists everything is fine.
This is the minimum needed to look.
Tables and chains #
The kernel's packet filter is organised into tables, each for a different kind of work, and each table has chains, which are lists of rules a packet walks through in order.
Two tables matter here:
- filter decides whether a packet lives or dies. This is what people mean by "the firewall".
- nat rewrites addresses and ports. This is how a published container port turns into a container's own address.
The filter table has three built-in chains, and which one your packet uses depends entirely on where it is going:
| Chain | Packets that use it |
|---|---|
INPUT |
arriving, addressed to this machine |
OUTPUT |
leaving, generated by this machine |
FORWARD |
passing through, on the way to somewhere else |
That third row is the one that surprises people. A packet being routed to a container is forwarded, not received, so it never touches INPUT. Rules you wrote for INPUT have no opinion about it.
Looking #
Two views, and it is worth using both.
$ sudo iptables -S
-S prints one compatibility view as commands. It is dense and useful for following UFW and Docker rules, but on nftables-based systems it is not necessarily the complete ruleset. Record your own output; do not expect a fixed number of lines or policies.
-P is a policy: what happens to a packet that reached the end of a chain without matching anything.
$ sudo iptables -L -n -v
-L lists in a table format, -n stops it trying to resolve every address and port to a name (which is slow and misleading), and -v adds the two most useful columns in the whole tool: how many packets and how many bytes have matched each rule.
Those counters are how you answer "is this rule doing anything". Try the connection, look again, see whether the number moved. If it did not, your packet is not reaching that rule and you are debugging the wrong thing.
Reading a rule #
-A ufw-user-input -s 192.168.1.0/24 -p tcp -m tcp --dport 22 -j ACCEPT
Left to right:
-A ufw-user-input: append to this chain-s 192.168.1.0/24: matching a source in that subnet-p tcp: matching TCP--dport 22: going to port 22-j ACCEPT: jump to this target
-j is the verdict, and there are four you will meet:
ACCEPT: let it throughDROP: discard silentlyREJECT: discard and send an error back- the name of another chain: go and evaluate that chain, then come back if it did not decide
That last one is how everything is organised. Chains jump to other chains, and a tool like ufw builds its own tree of them.
What ufw actually writes #
When you enable ufw, those three lines turn into a hundred or so. Do not be alarmed. The names are systematic:
ufw-before-*: rules that run before yours: connection tracking, loopback, ICMPufw-user-*: the rules you added withufw allowufw-after-*: the last word before the policyufw-logging-*: where dropped packets get recorded
So sudo iptables -S | grep ufw-user shows you your own rules in their raw form, which is a good way to check that a rule you wrote in ufw's friendly syntax means what you thought.
An early ufw-before-input rule normally accepts established and related traffic in a form like:
-A ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
That is connection tracking. Packets belonging to a conversation this machine already started are accepted before any of your deny rules get a look.
nftables, briefly #
nftables is netfilter's newer interface, and current Ubuntu commonly provides iptables through its nft compatibility backend. Check with iptables --version, then inspect the complete nft ruleset when that backend is active:
$ sudo nft list ruleset
The syntax and chain layout differ. Use ufw status, iptables -S and nft list ruleset as complementary views of the same effective policy rather than assuming one display contains everything.
The three questions worth asking #
When something is blocked and you do not know why:
- Which chain should this packet be in? Arriving for the machine is
INPUT. Going to a container isFORWARD. Getting this wrong sends you looking in the right tool at the wrong place. - Are the counters moving?
iptables -L -n -v, try the connection, look again. No movement means the packet is not getting there. - What matched first? Chains are evaluated in order and the first verdict wins. A permissive rule above a restrictive one means the restrictive one never runs.
Do not edit rules by hand on a machine you care about #
Rules written with iptables directly are gone at the next reboot unless something saves them, and they will confuse whichever tool believes it owns the firewall. Use ufw, or your configuration management, and read the raw rules only to understand what those tools did.
The one exception is debugging, where inserting a temporary rule and watching the counters is a perfectly good move. Take it back out afterwards.