skip to content

iptables

The classic netfilter frontend: tables, chains and rules, the order packets traverse them, NAT and masquerading, and stateful connection matching. Interviewers ask because iptables rules are still everywhere — Docker and Kubernetes generate them — and reading a rule set correctly depends entirely on knowing the traversal order.

on this pageshow

questions

5

On a Linux server you run `iptables -A INPUT -p tcp --dport 443 -j ACCEPT`, but the INPUT chain already ends with a catch-all `-j DROP` rule and port 443 is still unreachable. Explain why the new rule has no effect, and what you would run instead.

level: middleimportance: must knowfreq 72%

answer

  1. a chain is ordered, not a set
  2. append puts it where exactly?
  3. the first terminating match decides
  4. policy is not the same as a last rule

basics

~20 s

iptables walks a chain from top to bottom and stops at the first matching rule with a terminating target, so an ACCEPT appended below an existing catch-all DROP is never reached. Insert it above that rule with -I and a position instead of appending with -A.

solid answer

~50 s

`-A` means append, so the new ACCEPT lands at the very bottom of the INPUT chain — below the catch-all DROP. Chains are evaluated top to bottom and the first matching rule whose target is terminating decides the packet's fate, so the DROP fires first and traversal ends there. Nothing after it is ever consulted. The fix is to place the rule above the catch-all: list the chain with `iptables -L INPUT -n -v --line-numbers`, find the DROP's index, and insert at that index with `iptables -I INPUT <n> -p tcp --dport 443 -j ACCEPT`; `-I` with no number inserts at position 1. Worth noting the contrast that catches people out: if the chain had no catch-all rule and simply relied on a default policy of DROP, appending with `-A` would have worked fine, because the policy only applies when no rule matched at all. And confirm the fix with the `-v` counters — a rule at `0 0` while the failure continues is not being reached.

code

bash · 5 lines
bash
iptables -L INPUT -n -v --line-numbers
iptables -I INPUT 2 -p tcp --dport 443 -j ACCEPT
iptables -C INPUT -p tcp --dport 443 -j ACCEPT
iptables -Z INPUT
iptables -L INPUT -n -v --line-numbers

go deeper

for a junior

Recall that -A appends to the end and -I inserts, and that a chain is read top to bottom. Say plainly that a rule below a catch-all DROP will never be reached.

for a middle

Explain terminating versus non-terminating targets, why the first terminating match ends traversal, and the difference in outcome between an explicit catch-all rule and a default policy.

for a senior

Demonstrate that you verify rather than assume: zero counters, reproduce, read which rule moved, and use -C before adding so repeated runs stay idempotent.

for a principal

Argue for managing the whole ruleset declaratively from a reviewed file rather than by accumulated -A and -I calls, and explain why ordering that only exists in shell history is an operational risk.

## How a chain is evaluated An iptables chain is an ordered list, not a set. A packet entering the chain is offered to rule 1, then rule 2, and so on. For each rule the kernel checks the match criteria — protocol, ports, interfaces, addresses, whatever extensions the rule loaded — and if they all match, it applies the rule's target. If they do not match, evaluation moves to the next rule. The critical property is what happens on a match. Targets fall into two groups: - **Terminating targets** end traversal of the chain immediately: `ACCEPT`, `DROP`, `REJECT`. The verdict is decided; no later rule in that chain is consulted. - **Non-terminating targets** do something and then let evaluation continue: `LOG` and `NFLOG` are the ones you use most, and `RETURN` sends evaluation back to the calling chain. So "first match wins" is really "the first match with a terminating target wins". This is why order is not a stylistic detail in iptables — it is the whole semantics. ## Why the appended rule is dead `-A` (`--append`) puts a rule at the end of the chain. With the chain already ending in a catch-all `-j DROP`, appending puts your ACCEPT after it: ``` 1 ACCEPT tcp -- ... dpt:22 2 DROP all -- ... <- catch-all, terminating 3 ACCEPT tcp -- ... dpt:443 <- your new rule, unreachable ``` A packet for port 443 matches rule 2 (a catch-all matches everything), gets DROPped, and traversal ends. Rule 3 is unreachable code. Its counters will sit at `0 0` forever, which is exactly the evidence that confirms the diagnosis. ## The fix: insert at a position `-I` (`--insert`) takes an optional rule number and places the rule *at* that position, shifting everything from there down: ```bash iptables -L INPUT -n -v --line-numbers # find the DROP's index, say 2 iptables -I INPUT 2 -p tcp --dport 443 -j ACCEPT # new rule becomes rule 2 iptables -L INPUT -n -v --line-numbers # verify, then watch the counters move ``` `iptables -I INPUT <rule>` with no number is equivalent to `-I INPUT 1` — top of the chain. That is convenient and occasionally wrong: putting an ACCEPT above a rule that was deliberately blocking a hostile source changes the meaning of the whole chain. Read the chain before you insert into it. ## Catch-all rule versus default policy — the distinction that decides this Every built-in chain has a **default policy**, set with `-P` (for example `iptables -P INPUT DROP`) and shown in the chain header of a `-L -v` listing. The policy applies to a packet only when it has traversed the entire chain without matching any terminating rule. That produces two superficially identical setups with opposite behaviour when you append: - **Policy `DROP`, no catch-all rule.** `-A INPUT ... -j ACCEPT` works. The packet walks the chain, matches your new last rule, and is accepted before the policy is ever consulted. - **Policy anything, with an explicit catch-all `-j DROP` at the end.** `-A` never works, because the catch-all is a rule and it fires first. When someone says "I added the rule and it did nothing", this is nearly always the difference they missed. ## Verifying instead of hoping Three commands turn this from guesswork into evidence: - `iptables -L INPUT -n -v --line-numbers` — the counters on your rule tell you whether traffic reaches it. - `iptables -Z` — zero the counters, reproduce the failure, then read them, so the numbers describe only this attempt. - `iptables -C INPUT -p tcp --dport 443 -j ACCEPT` — check whether an identical rule already exists. Useful before adding, because repeated `-A` calls in a provisioning script happily create duplicates that all sit in the wrong place. ## Editing without making it worse `-R INPUT <n> <rule>` replaces the rule at position `n` in place, which keeps ordering stable and is safer than delete-then-add when the chain is live. `-D` accepts either a position (`iptables -D INPUT 3`) or the full rule specification (`iptables -D INPUT -p tcp --dport 443 -j ACCEPT`); the specification form is what you want in scripts, because positions shift under you the moment anything else changes the chain. When you delete several rules by number, work from the highest number downward. Deleting rule 3 renumbers the old rule 4 to 3, so a top-down loop removes rules you never intended to touch. ## The habit that avoids the whole class of bug Once a ruleset is more than a handful of rules, stop patching it imperatively. Keep the intended ruleset as a file and apply it whole, so ordering is something you read in a diff rather than something you reconstruct from a sequence of `-A` and `-I` calls made months apart.

  • If the chain had a default policy of DROP but no catch-all rule at the end, would the appended ACCEPT have worked?
    Yes. The default policy is consulted only after a packet has walked the whole chain without matching a terminating rule. With no catch-all present, the appended ACCEPT is reached, matches, and terminates traversal before the policy applies. That is precisely why two chains that both "block everything by default" can behave differently when you append to them.
  • What is the difference between `-I INPUT` and `-I INPUT 1`, and when is inserting at the top the wrong move?
    They are the same — `-I` defaults to position 1. It is the wrong move whenever earlier rules exist to block something deliberately: an ACCEPT jumped to the top of the chain can override a block on a hostile source or a rate limit that was carefully placed first. Read the chain and insert at a chosen position rather than reflexively at the top.
  • You add the same rule twice with `-A` in a provisioning script. What is the consequence, and how do you avoid it?
    You get duplicate rules, which waste evaluation and, worse, make the chain's real ordering hard to reason about later. Guard with `iptables -C <chain> <rule>` which returns success only if the rule already exists, or stop patching imperatively and apply a complete ruleset from a file so the result is idempotent by construction.

A chain is a stack of paper forms a clerk reads top to bottom, stamping the first one that applies and filing the packet immediately. Putting your form at the bottom of the stack, under a form that matches everything, means it is never read.

saying these in an interview costs you the question

  • Thinks iptables rules are a set where order does not matter
  • Believes -A appends but somehow still takes priority
  • Confuses the chain's default policy with a final catch-all rule
  • Says the rule must be wrong rather than unreachable
  • Deletes and re-adds rules by number without re-listing

context

open as a page

On a Linux host, `iptables -L` takes tens of seconds before it prints anything. Why does listing rules stall like that, and what do the `-n`, `-v` and `--line-numbers` options change about the output?

level: juniorimportance: should knowfreq 48%

basics

~20 s

iptables -L reverse-resolves every address in the ruleset and turns port numbers into service names, so it stalls whenever DNS is slow or dead. Adding -n prints raw numbers, -v adds packet and byte counters plus interface columns, and --line-numbers indexes each rule.

open as a page

iptables rules you added on a Linux server work immediately but are gone after a reboot. Where do those rules actually live, how do you make them persist on a Debian/Ubuntu and on a RHEL-family host, and what is most often forgotten?

level: middleimportance: should knowfreq 58%

basics

~20 s

iptables rules live only in kernel memory, so a reboot clears them. Dump them with iptables-save and reload with iptables-restore, wired to boot by iptables-persistent on Debian/Ubuntu or iptables-services on RHEL. The IPv6 ruleset is separate and is the thing most often forgotten.

open as a page

You are SSH'd into a remote Linux server that has no console access, and you are about to run `iptables -P INPUT DROP`. What is the risk, and how do you make that change without locking yourself out?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The policy takes effect instantly for every packet that matches no rule, so unless rules already accept your SSH traffic, the session dies and you cannot reconnect. Put those accept rules in first, verify them with a second session, and arm an automatic rollback before changing the policy.

open as a page

Connections to a service on a Linux host are disappearing with no reply and you suspect the local iptables rules. Using iptables itself, how do you determine which rule is responsible?

level: seniorimportance: nice to knowfreq 34%

basics

~20 s

Every iptables rule carries kernel-maintained packet and byte counters. Zero them with iptables -Z, reproduce the failure, then list with -L -n -v --line-numbers and see which rule's counters moved. Insert a rate-limited LOG rule above the suspect to capture the packets themselves.

open as a page