Side reading

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 through
  • DROP: discard silently
  • REJECT: 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, ICMP
  • ufw-user-*: the rules you added with ufw allow
  • ufw-after-*: the last word before the policy
  • ufw-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:

  1. Which chain should this packet be in? Arriving for the machine is INPUT. Going to a container is FORWARD. Getting this wrong sends you looking in the right tool at the wrong place.
  2. Are the counters moving? iptables -L -n -v, try the connection, look again. No movement means the packet is not getting there.
  3. 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.

Settings

Your values

The book is written with placeholder names so it makes sense to everybody. Put your own in and every chapter, every command and every copy-paste prompt updates to match.

Nothing here is sent anywhere. It is saved in this browser, so it comes back next time. A different browser or a private window gets the placeholders again.

Live preview

$ ssh admin@192.168.1.20
$ sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
$ sudo hostnamectl set-hostname homeserver
$ sudo timedatectl set-timezone Europe/Paris

Real commands from chapters 2, 3 and 4. They change as you type.

The account you log in as. Not root, and not necessarily the same name you use on your laptop.

Introduced in Chapter 2, Meet your server

The book's placeholder is admin

What the machine calls itself. You choose it, and it shows up in your shell prompt and your logs.

Introduced in Chapter 3, A safe front door

The book's placeholder is homeserver

The IP address your server has on your home network, from ip -brief addr.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.20

The address range and prefix shown by ip route or ip -brief addr, written in CIDR form. Copy the real prefix; do not guess /24.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.0/24

The address traffic goes to on its way out of your house, from ip route.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.1

In Region/City form, or Etc/UTC if you would rather read logs in UTC.

Introduced in Chapter 3, A safe front door

The book's placeholder is Europe/Paris

A registered name you control. Chapter 8 uses it for the LAN route; chapter 12 uses a separate private Tailscale name remotely.

Introduced in Chapter 8, One door, many rooms

The book's placeholder is example.com

The email identity allowed to administer the tagged server in your Tailscale policy.

Introduced in Chapter 10, Your own private network

The book's placeholder is you@example.com

The mailbox that should receive actionable home-server alerts.

Introduced in Chapter 14, Knowing it is alive

The book's placeholder is alerts@example.com

Once you save, the prose and the commands read with your names, the copy buttons copy your values, and the copy-paste prompts describe your machine accurately. That last one matters: an assistant told your network is 192.168.1.0/24 when it is not will send you chasing the wrong thing.

Anything you leave empty keeps the book's placeholder.