Skip to content

Firewall

A router forwards a packet toward its destination without asking whether that packet should be allowed through at all — that's not its job. A firewall exists specifically to answer that question, at whatever point in the network it sits, and the way it answers has changed substantially since the idea first appeared.

The basic decision: allow or deny, based on a rule

At its simplest, a firewall inspects each packet against an ordered list of rules and either allows it to pass or drops (or explicitly rejects) it. A rule typically matches on some combination of source IP, destination IP, source port, destination port, and protocol (TCP, UDP, ICMP). This style — evaluate each packet independently against static rules, with no memory of what came before — is called packet filtering, and it's genuinely the oldest form of firewall, dating to the late 1980s.

Packet filtering alone has a real limitation, and it's worth seeing exactly where it breaks down. Suppose a rule allows outbound traffic to port 443 (HTTPS) so your browser can reach the web. The response from that web server has to come back in — a packet with a source port of 443 and a destination port matching whatever ephemeral port your browser used. A purely stateless filter has no way to distinguish "this is a legitimate reply to a connection I permitted a moment ago" from "this is an unsolicited packet claiming to come from port 443," because it evaluates every packet in isolation, with no record of what request it might be a response to. Writing a rule broad enough to allow all the legitimate replies ends up allowing a lot of illegitimate traffic too.

Stateful inspection: remembering the connection

Stateful inspection, which became the dominant approach through the 1990s, fixes this by tracking active connections rather than judging every packet alone. When a stateful firewall sees the first packet of a new connection — a TCP SYN, for instance — and that packet matches an allow rule, it creates an entry in a connection tracking table recording the connection's source, destination, ports, and protocol. Every subsequent packet that matches an existing entry is allowed through automatically, without being re-checked against the full rule set, because it's already been established as part of a permitted conversation. A packet that doesn't match any tracked connection and doesn't match an explicit allow rule gets dropped.

This is exactly the mechanism behind a detail worth calling out explicitly: a stateful firewall rule that allows outbound connections to port 443 automatically permits the inbound replies on that same connection, without needing a separate inbound rule for them at all. The state table is what makes that possible — the firewall isn't reasoning about ports in isolation, it's reasoning about a connection it already decided to trust.

sudo conntrack -L
tcp      6 431999 ESTABLISHED src=192.168.1.42 dst=93.184.216.34 sport=51230 dport=443 src=93.184.216.34 dst=192.168.1.42 sport=443 dport=51230 [ASSURED] mark=0 use=1

That single line is the connection-tracking entry making the whole "reply traffic just works" behavior possible — it records both directions of the flow as one tracked conversation, and the 431999 is the number of seconds left before this entry expires from the table if the connection goes idle.

Beyond Layer 4: next-generation firewalls

A stateful firewall still only sees IP addresses, ports, and protocol — it has no idea what's actually inside the traffic it's tracking. A next-generation firewall (NGFW) adds inspection further up the stack: it can read the application-layer content itself, identify which specific application generated traffic regardless of what port it's using, apply intrusion-prevention signatures, and in some deployments decrypt and re-encrypt TLS traffic on the fly to inspect what's actually inside it — intercepting the connection much like a device standing in for the real destination. This is a meaningfully different capability from stateful inspection, not just a marketing label for a slightly better version of it: a stateful firewall can tell you a connection is going to port 443; an NGFW can tell you whether the traffic on that connection actually matches a known malware signature, because it's reading past the transport-layer headers into the payload itself.

The trade-off is cost, in both hardware and latency — deep packet inspection, reading and evaluating the actual content of every packet, is considerably more expensive per packet than checking a connection-tracking table. This module returns to that same trade-off, between inspecting only headers and inspecting full content, when it covers load balancers.

Where a firewall sits

A firewall can run as a dedicated appliance at the edge of a network, as software on an individual host (a host-based firewall, filtering traffic for that one machine specifically), or as a feature built into a router. All three enforce the same basic idea — allow or deny, based on rules — at different points in the path a packet takes.

Packet filtering Stateful inspection NGFW
Decision basis Each packet, alone Tracked connection state Connection state + payload content
Sees inside TLS No No Only if configured to terminate it
Relative cost Lowest Moderate Highest
Typical era Late 1980s onward 1990s onward 2010s onward

Practical scenario: a service that's listening but unreachable

A developer deploys a new API on a cloud VM, confirms the process is running and bound to 0.0.0.0:8443 (not 127.0.0.1, ruling out the classic loopback-binding mistake), and confirms with ss -tulpn that the socket is actually listening. Locally, on the VM itself, curl https://localhost:8443/health returns a healthy response. From any other machine, the same request just hangs until it times out — no connection refused, no TLS error, nothing. It behaves exactly like the packet vanished.

A timeout instead of an immediate refusal is the tell. A closed port produces a fast, explicit rejection — the operating system responds right away saying nothing is listening there. Traffic that's silently dropped by a firewall rule produces exactly this symptom instead: the client sends a SYN, gets no response of any kind, and has to wait out its own retransmission timeouts before giving up. Checking the host's own firewall confirms it:

sudo nft list ruleset
table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        tcp dport 22 accept
        tcp dport 443 accept
    }
}

The default policy is drop, and there's no rule permitting inbound traffic to port 8443 — only 22 and 443 are explicitly allowed, and the new service's port was never added when it was deployed. The fix is adding a rule for it, but that's a change worth making carefully rather than immediately, since a mistyped rule on the very firewall protecting a remote host's only access path can lock out the administrator along with everyone else.

Warning

Editing a remote host's firewall rules over the same connection you're using to reach it can cut off your own access if the new ruleset doesn't include a rule for it. Before applying a change: confirm you have out-of-band access (a cloud console, IPMI, or physical access) as a fallback, validate the ruleset syntax first (nft -c -f <file> checks a file without applying it), and keep the working SSH rule in place through the whole change rather than replacing the ruleset wholesale.

Common mistakes

  • Confusing "no response" with "the service is down." A closed port and a firewall-dropped port look identical from the client's point of view in the sense that neither works — but a closed port refuses immediately, while a dropped port times out. That difference in how it fails is the fastest clue to where the problem actually is.
  • Adding a broad allow rule to make a problem go away, without checking whether it's actually needed. A rule that allows all traffic on a port "just in case" is often still there years later, long after whatever debugging session justified it, and each one is a durable increase in what the firewall no longer protects against.

Practice exercises

  1. Explain, using the connection-tracking concept from this article, why a stateful firewall doesn't need a separate inbound rule to let replies through on a connection it already permitted outbound.
  2. A colleague reports "the server refuses my connection instantly" for one service and "the connection just hangs" for another. Based only on that description, which one is more likely blocked by a firewall rule, and why?
  3. Look up (or, in a disposable lab VM, run) sudo ss -tulpn and sudo nft list ruleset or sudo iptables -L -n on a Linux host you control, and identify whether every listening port shown by the first command has a corresponding allow rule in the second.

A firewall decides whether traffic is allowed through at all, based on addresses, ports, and — for an NGFW — the traffic's actual content. It has no concept of sharing that traffic intelligently across multiple servers once it's been allowed in; that's a different problem, and a different device, covered next.

Sources