Skip to content
Infrastructure · 5 min read

Your firewall should default to deny and log the rest

The claim A firewall configured to block a list of bad things and allow everything else is protecting you from yesterday's threats and nothing more, because the list can never be c...

A Written by Administrator
Your firewall should default to deny and log the rest

The claim

A firewall configured to block a list of bad things and allow everything else is protecting you from yesterday's threats and nothing more, because the list can never be complete. A firewall configured the other way — deny everything, then allow the specific, named things your system actually needs — protects you from every port and service you forgot you were running, which is exactly where intrusions get in. Default-deny is not a stricter version of the same idea; it is the correct model, and default-allow is a mistake that happens to work until it does not.

Why default-allow cannot be made safe

The problem with allowing everything except a blocklist is that you are responsible for anticipating every dangerous thing in advance, and you cannot. A service you installed for a trial and forgot to remove is listening on a port. A debugging interface someone opened during an incident was never closed. A dependency started a management server on a high port you did not know about. Under default-allow, every one of these is reachable from the internet by default, and it stays reachable until you happen to notice it — which, in practice, is often when an attacker notices it first.

Default-deny inverts the burden. Nothing is reachable unless you explicitly decided it should be, so the forgotten trial service, the debugging interface, and the surprise management server are all closed automatically, without you having to know they exist. The security no longer depends on your memory being complete.

The configuration

Every firewall expresses this, and the shape is always the same: set the default policy to drop, then add the specific allowances.

# nftables: deny by default, allow only what is needed
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;   # the key line

    ct state established,related accept   # replies to our own requests
    iif "lo" accept                       # loopback
    tcp dport 22 ip saddr 10.0.0.0/8 accept   # SSH from internal only
    tcp dport { 80, 443 } accept          # the public web service
    # everything else: dropped by the default policy above
  }
}

The policy drop line is the whole philosophy in three words. After it, each rule opens exactly one door, and the doors you did not open stay shut. Note that SSH is restricted by source address, not open to the world — the management port should almost never be reachable from the whole internet, and default-deny makes that restriction the natural default rather than an extra step.

The rule that makes it maintainable: name why each port is open

A default-deny ruleset accumulates allow rules over time, and without discipline it decays into a list nobody understands, at which point people stop removing rules for fear of breaking something. Every allow rule should carry a comment saying what it is for and, ideally, who owns it, so that a year later you can audit the list and confidently close what is no longer needed.

tcp dport 5432 ip saddr 10.0.2.0/24 accept   # app tier -> Postgres (owner: platform)
tcp dport 9100 ip saddr 10.0.9.5 accept      # metrics scraper only  (owner: platform)

An allow rule you cannot explain is one you should be able to remove, and the comment is what lets you tell the difference between a rule that is load-bearing and one that is a forgotten leftover.

Log the denials, because they are your early warning

A default-deny firewall silently dropping unwanted traffic is safe, but silent, and the dropped traffic is information you want. Logging denied connections turns the firewall into a sensor: a sudden burst of drops against a range of ports is a port scan, repeated drops from one source is someone probing you, and drops against a port you thought was closed confirm the default is doing its job. Log with a rate limit so a flood does not fill the disk:

log prefix "fw-drop: " limit rate 10/second

Send those logs to your central store, and the pattern of what is being refused becomes one of the cheapest threat signals you have — you are watching, in real time, what the internet is trying to reach that you have correctly denied.

Apply it in both directions

Most firewalls default to filtering inbound traffic, but the same principle applied to outbound traffic is a strong containment measure that few small teams use. If a host is only supposed to talk to your database and a payment API, an outbound default-deny that permits exactly those destinations means a compromised process on that host cannot exfiltrate data to an arbitrary server or download a second-stage payload, because the connection it needs to make is denied by default. Outbound filtering takes more care to configure without breaking legitimate traffic, so introduce it deliberately — but on a host handling sensitive data, it converts a full compromise into a contained one.

Where to start

Look at your most exposed host and answer one question: is its firewall default-allow or default-deny? If you cannot tell, it is almost certainly default-allow, because default-deny is something you set on purpose and remember doing. Convert it — carefully, with console access in case you lock yourself out, listing the ports genuinely in use before you flip the policy. Then log the denials and watch for a week. You will almost always discover something reachable that should not have been, and closing it is the single clearest security improvement most small deployments can make in an afternoon.

#security #firewall #networking #operations

Keep reading