Skip to content
Go back

The Firewall Rule Order That's Breaking Your Setup

By SumGuy 4 min read
The Firewall Rule Order That's Breaking Your Setup

Here’s the thing: your firewall might be completely open and you don’t even know it. I’ve walked into production environments where someone added a broad ALLOW rule “just to test something” and never removed it, sitting above all the restrictive rules. By the time the audit rolled around, they’d forgotten it was there.

The fundamental principle is this: firewall rules are evaluated top-down, and the first match wins. Every rule below a matching rule is ignored. That one fact will save you from shooting yourself in the foot.

The Classic Mistake

Here’s what someone inexperienced usually does:

Terminal window
$ sudo ufw allow from 10.0.0.0/8
$ sudo ufw deny from 10.0.1.100

They think: “Allow the whole subnet, then deny one bad actor.” Nice try. The second rule never fires because the first rule already matched. Anyone from 10.0.0.0/8 gets through, including 10.0.1.100.

The correct order:

Terminal window
$ sudo ufw deny from 10.0.1.100
$ sudo ufw allow from 10.0.0.0/8

Now the specific denial happens first, then the broader allow. The bad actor is blocked, everything else from that subnet gets through.

Why Order Matters: iptables Under the Hood

UFW is a wrapper around iptables, so let’s think about what’s actually happening. When a packet arrives, iptables walks down the rule list in order and stops at the first match. If you have:

Terminal window
# Rule 1 (top)
iptables -A INPUT -p tcp -m multiport --dports 22,80,443 -j ACCEPT
# Rule 2
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.50 -j DROP
# Rule 3
iptables -A INPUT -j DROP

A connection from 192.168.1.50:22 hits Rule 1 first (port 22 is in the multiport list) and gets ACCEPT’d immediately. Rule 2 never sees it. Your attempt to block SSH from that IP is completely ignored.

The fix: be specific before being broad.

Terminal window
# Rule 1 (top) — specific denials first
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.50 -j DROP
# Rule 2 — then the broad allow
iptables -A INPUT -p tcp -m multiport --dports 22,80,443 -j ACCEPT
# Rule 3 — default deny
iptables -A INPUT -j DROP

Checking Your Current Rules (UFW)

List all rules in order with:

Terminal window
$ sudo ufw status numbered

You’ll get something like:

To Action From
-- ------ ----
[ 1] 22 ALLOW Anywhere
[ 2] 443 ALLOW Anywhere
[ 3] 80 ALLOW Anywhere
[ 4] 22 DENY 192.168.1.50

See that? Rule 4 is useless because Rule 1 already matched port 22 from anywhere, including 192.168.1.50. You need to delete and reinsert it:

Terminal window
$ sudo ufw delete 4
$ sudo ufw insert 1 deny from 192.168.1.50 to any port 22

The insert 1 puts it at the top. Now it looks like:

[ 1] 22 DENY 192.168.1.50
[ 2] 22 ALLOW Anywhere
[ 3] 443 ALLOW Anywhere
[ 4] 80 ALLOW Anywhere

Much better.

Real-World Pattern: Deny-First Rules

Here’s the pattern your production firewall should follow:

Terminal window
# 1. Deny specific bad actors / subnets
sudo ufw deny from 203.0.113.0/24 # sketchy IP range
sudo ufw deny from 192.0.2.50 # compromised internal box
# 2. Allow specific internal subnets to SSH
sudo ufw allow from 10.0.1.0/24 to any port 22
sudo ufw allow from 10.0.2.0/24 to any port 22
# 3. Allow public services
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# 4. Default deny everything else
sudo ufw default deny incoming

Notice: specific denials, then specific allows, then broad allows, then default deny. That’s your hierarchy.

One More Gotcha: In vs Out

By default, UFW defaults to denying incoming traffic. But your outbound rules are separate. You might be allowing SSH in, but if you’ve also set default deny on outgoing and never allowed traffic out, your syslog is full of dropped packets going from your machine:

Terminal window
$ sudo ufw default deny outgoing
$ sudo ufw allow out 53 # DNS
$ sudo ufw allow out 443 # HTTPS

Now your box can’t talk to anything except DNS and HTTPS. Double-check both directions:

Terminal window
$ sudo ufw show added

The Audit Moment

Every few months, export your rules and actually read them:

Terminal window
$ sudo ufw show added > /tmp/firewall_rules.txt
$ cat /tmp/firewall_rules.txt

Search for broad ALLOW rules sitting above denials. Search for commented rules you forgot you added. Search for test rules from 6 months ago.

Your 2 AM self (or your auditor) will thank you.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it may appear here.


Previous Post
Where Environment Variables Actually Live in Linux
Next Post
Sticky Bit, Setuid, Setgid: Linux Special Permissions Explained

Related Posts