A Firewall Is a Bouncer Who Only Knows Numbers
It cannot read intent, only addresses and ports, and that limitation is the entire design

Contents
A firewall gets talked about as though it makes judgement calls about traffic — blocking “suspicious” connections, letting through “legitimate” ones. It doesn’t do anything of the sort, and believing it does is how people end up with rules that feel secure and aren’t. A firewall is a bouncer standing at a door who has never met anyone and never will. All they have is a clipboard of numbers: this address, that port, this protocol. They check the numbers against a rulebook and wave the connection through or turn it away. They have no idea what the traffic is actually for, and a rule written as though they did is a rule that will eventually let the wrong thing through, or block the right thing, for reasons that look inexplicable until you remember the bouncer can’t read.
What the bouncer can actually see
A traditional firewall, operating at the network and transport layers, inspects a small, fixed set of fields on every packet: source IP, destination IP, source port, destination port, and protocol (TCP, UDP, ICMP). That’s the entire clipboard. It does not know that a packet destined for port 443 contains a request for /admin rather than /. It does not know that the client claiming to be a browser is actually a script. It does not know that the “user” behind a source IP has already failed nine login attempts, unless something else — fail2ban, CrowdSec, an application layer — is watching that and feeding the firewall new numbers to add to its list.
This is why “firewall rules” and “application security” are different jobs done by different tools, and why a firewall alone has never been sufficient defence for anything with a web interface exposed to the internet. The firewall’s job is to make sure only the doors you intended are open at all. What happens once someone walks through an open door — whether the login form has rate limiting, whether the API validates its inputs — is entirely outside a firewall’s field of view. A deep packet inspection firewall extends the clipboard a little further, peeking at some application-layer signatures, but even that is pattern matching on numbers and bytes, with no comprehension of what the traffic means.
Stateful versus stateless: does the bouncer remember you left five minutes ago
An early, stateless firewall evaluates every single packet in isolation against the rulebook, with no memory of what came before. This sounds fine until you realise a TCP connection is a conversation, not a single message — a client sends a SYN, the server replies SYN-ACK, the client replies ACK, and only then does actual data flow. A stateless firewall has to have an explicit rule allowing the return traffic for every connection you want to permit outbound, because it has no concept of “this packet is a reply to something we already approved.”
A stateful firewall keeps a connection tracking table — a running list of which conversations are already in progress, keyed by source and destination IP and port. Every modern firewall worth using, from nftables on a Linux box to a dedicated appliance like OPNsense, works this way, and it’s why a single rule like “allow outbound to anywhere, allow established and related traffic back” is enough to let your own outbound web browsing work without a separate inbound rule for every possible reply. The bouncer isn’t reading minds here — they’re just keeping a list of who they already waved through the front, so they know to wave the same person back out without re-checking the whole rulebook.
Rule order: the bouncer reads top to bottom and stops at the first match
This trips up more homelab firewalls than any other single thing. Firewall rules are evaluated in order, and — depending on the implementation — the first matching rule wins and evaluation stops there. A broad “allow” rule sitting above a narrower “deny” rule for the same traffic means the deny rule never gets its turn; the bouncer already waved the person through two rules ago and never reads the rest of the clipboard for that particular person.
This is the single most common reason a firewall “isn’t working” despite the rule clearly being present in the config: it’s present, correct, and unreachable, because something above it already matched. The fix is always the same regardless of firewall software — read the ruleset top to bottom in the order it’s actually evaluated, not the order that feels logical, and put your most specific rules above your most general ones.
Default deny versus default allow: the bouncer’s resting posture
Every firewall has to decide what happens when no rule matches at all. Default allow lets unmatched traffic through; default deny blocks it. Consumer routers running out of the box are usually a strange hybrid — default deny on the WAN-to-LAN direction (nothing gets in unsolicited) and default allow on LAN-to-WAN and LAN-to-LAN (everything inside can talk to everything else and reach the internet without restriction), which is exactly why a compromised smart bulb on a home network can usually see and probe every other device on it without hitting a single rule.
A genuinely defended home network flips that internal default to deny as well, and only opens the specific paths that need to exist — which is most of the actual value in segmenting a home network into VLANs rather than leaving every device on one flat subnet. The firewall doesn’t get smarter when you do this. It’s still just checking numbers. What changes is that there are now more boundaries with rulebooks at them, so a device on one segment can’t reach a device on another unless a rule explicitly says so, rather than by default.
A worked example with nftables
Here’s a small, realistic ruleset that expresses everything above in about a dozen lines:
| |
policy drop sets the default posture to deny — anything not explicitly matched falls through to the end and gets dropped. ct state established,related accept is the stateful shortcut from earlier: once a connection is already tracked, don’t re-evaluate every packet in it against the whole ruleset. The SSH rule is scoped to a specific source subnet rather than left open to the world, which is the difference between “I can manage this box from home” and “anyone on the internet can attempt to log in.” ct state invalid drop at the end catches malformed packets that don’t correspond to any real connection state — a cheap, effective filter against a category of scanning traffic that a purely stateless ruleset would have to handle with much clumsier rules. nftables replaced iptables as the standard Linux packet filter for exactly this kind of readability, and if you’re still thinking in the older syntax, the mental model transfers directly — I’ve covered the move from iptables to nftables in more depth elsewhere.
NAT: the bouncer swapping name badges at the door
Network address translation gets bundled together with firewalling so often that people assume it’s the same mechanism. It isn’t, though the two usually live in the same box and cooperate closely. NAT rewrites the source or destination address on a packet as it crosses a boundary — typically swapping a private internal address for a single public one on the way out, and reversing that translation on the way back in, using the connection tracking table to remember which internal address a given reply belongs to. The bouncer isn’t deciding who gets in here; they’re handing out a shared name badge that says “guest of the building” to everyone leaving, then matching replies addressed to “guest of the building” back to the specific person who actually left.
This is why NAT accidentally provides a security benefit despite not being a security feature: a device behind NAT has no publicly routable address of its own, so nothing on the internet can address it directly without the router first being told to forward a specific port inward. Port forwarding is the explicit exception to that — a standing rule that says “any traffic arriving on the public IP at port X, rewrite it and hand it to this specific internal address,” which is exactly the kind of rule that deserves to be as narrow as the service actually requires, since it’s a deliberately punched hole rather than a byproduct of normal outbound traffic.
Egress filtering: the bouncer nobody bothers to hire
Most home firewalls, and a fair number of business ones, invest all their rule-writing energy in what’s allowed to come in and almost none in what’s allowed to go out. Default allow on outbound traffic means that once anything inside the network is compromised — a smart device with a firmware backdoor, a laptop that picked up malware from a bad download — it can talk to any destination on the internet on any port, and the firewall, still just checking numbers, has no rule telling it not to.
Egress filtering flips this: an explicit allow-list of which internal devices are permitted to reach the internet at all, and on which ports. A smart camera that only ever needs to reach its manufacturer’s cloud service on port 443 gets exactly that rule and nothing broader; if it’s ever compromised and tries to phone home to an unexpected address or open a reverse shell on an unusual port, the firewall blocks it because the destination didn’t match any rule on the clipboard, having never recognised the malware in the first place. This is one of the more neglected parts of a proper home network threat model — inbound protection gets all the attention, and outbound is often left wide open by default on both consumer routers and a fair number of homelab setups that never got around to it.
Troubleshooting: when the rulebook doesn’t do what you expect
The most common complaint is “I added a rule and it isn’t working,” which is almost always rule order — a broader rule above it already matched and stopped evaluation. Listing rules with line numbers (nft -a list ruleset or the equivalent for your firewall) and checking counters on each rule shows you exactly which one is actually being hit, which ends the guesswork faster than staring at the config.
The second is “outbound works but nothing can reply,” which points at a missing or misconfigured established,related rule, or a stateful tracking table that’s been disabled or is dropping state for a specific protocol like UDP-based traffic that doesn’t have a clean notion of “established” the way TCP does. UDP is connectionless, so “established” for it is really a timeout-based heuristic — the firewall remembers a UDP conversation for a short window and matches replies against that, and if the window is too short for a slow round trip, the reply gets treated as unsolicited and dropped.
The third is a genuinely silent failure: a default-drop policy with no logging, so blocked traffic disappears without a trace and you’re left guessing whether the request never arrived, was blocked, or the service on the other end is simply down. A temporary logging rule just above the final drop — even something as blunt as logging everything that reaches that point before it’s discarded — turns an invisible problem into one line in a log file, and it costs nothing to remove once you’ve found the answer.
Is a “serious” firewall worth it at home
A consumer router’s built-in firewall handles the one job most homes actually need: keeping unsolicited traffic from the internet out. If that’s the entire threat model, it’s already doing it, and nothing here changes that. Where it falls short is internal segmentation and visibility — knowing what’s actually being blocked, and stopping one compromised device on the network from freely reaching every other one. A dedicated firewall running OPNsense or a similar platform earns its keep the moment you have more than one class of device you’d rather not trust equally, which for most home networks with a handful of IoT gadgets on it is nearly all of them. It’s still just checking numbers against a rulebook. The value is in writing a rulebook that actually reflects who should be talking to whom, rather than trusting the factory default of “everyone inside trusts everyone else inside.” Running a dedicated box for this, rather than relying on a consumer router’s cut-down firmware, also buys you the connection-tracking table depth, logging, and rule granularity that the worked example above actually needs room to express — a factory router’s web interface rarely exposes anything close to per-subnet egress rules, however capable the underlying chipset might technically be.




