Firewall hygiene
Most firewalls carry rules that cannot match a packet
Not wrong rules. Dead ones. They sit in the config, they appear in the audit export, and someone reads one while troubleshooting and concludes something false about their own network. This page covers how they accumulate, how to find them by hand on each platform, and where an auditor helps.
Why a rule goes dead
Rules are evaluated in order. If a rule above already covers the entire address and port space of the one below it, the lower rule is never reached. Nothing errors. Nothing warns you. The rule simply stops being part of your policy while remaining part of your config.
The evaluation order that makes this happen is not identical everywhere, and the difference matters when you audit:
| Platform | Evaluation | Audit implication |
|---|---|---|
| iptables / nftables | First match wins within a chain | Also follow jumps between chains — shadowing can happen across a jump, not just within one list. |
| Cisco ASA | First match wins per ACL | Check which ACL is actually bound with access-group. Unbound ACLs are dead in a different way. |
| FortiGate | First match wins | Policies are per-VDOM and per interface pair; compare only within the same scope. |
| pfSense / OPNsense (pf) | Last match wins — unless the rule is marked quick | The GUI marks rules quick by default, so it behaves first-match. Hand-written rules in the raw ruleset may not. |
That pf row is the one that catches people. If you audit a pfSense box by reading the rule list top-down with first-match assumptions, and some rules are not quick, your conclusions will be wrong in both directions — you will call live rules dead and dead rules live.
How they accumulate
Nobody sets out to build a config like this. It happens the same way everywhere:
- One emergency rule added at 2am, above everything, wide enough to definitely work.
- One vendor port opened for an install and never closed.
- One migration where the old ruleset was copied in "to be safe" and appended.
- One ticket that asked for a /32 and got a /24 because the requester was not sure.
A question worth asking wherever you work: when did anyone last delete a firewall rule, rather than add one? In most places the honest answer is that deletion requires a confidence nobody has, because nobody can prove what a rule is doing.
What to look for
Shadowed rules
A rule whose entire match space is covered by an earlier rule. It can never be reached. If the earlier rule has the opposite action, this is usually a real misconfiguration — someone believes traffic is permitted, or denied, and it is not.
Redundant rules
Covered by an earlier rule with the same action. Harmless to traffic, but it inflates the ruleset, slows review, and hides the rules that matter.
Over-permissive rules
Rules far broader than the change that created them. any in a source, a /16 where a /29 would do, a port range where one port would do. These are the rules that turn a small compromise into a large one.
Drift
What changed since the last known-good baseline, and who changed it. Drift detection is the only one of these four that catches a problem as it appears rather than months later.
Doing it by hand
For a small ruleset this is entirely reasonable:
- Export the running config, not the documentation. They diverge.
- Expand every object and group to literals. Shadowing is invisible while rules refer to named groups whose contents overlap.
- Compare each rule against every rule above it — source set, destination set, protocol, port set. Full containment above means dead below.
- Check hit counters where the platform has them (
show access-liston ASA,iptables -nvL, FortiGate policy hit count). A zero counter over a long window is a strong hint — but not proof, because the traffic may simply be seasonal. - Diff against your baseline and reconcile every difference to a change ticket.
Step 3 is where it stops being reasonable. Comparing every rule against every rule above it is quadratic, and with expanded object groups a few hundred rules becomes tens of thousands of set comparisons. That is the part worth automating — not because it is hard, but because humans do it inconsistently at scale.
RuleHawk
A self-hosted firewall config auditor. It parses iptables, Cisco ASA, pfSense/OPNsense and FortiGate configs, expands objects to literals, and reports shadowed rules, over-permissive allows, hygiene problems and drift against a baseline.
It is fully offline. Zero outbound connections — it runs air-gapped, which matters because a firewall config is a map of your network and the last thing it should do is leave it.
Run it
Grab the tarball and SHA256SUMS from the releases page, then:
sha256sum -c SHA256SUMS
tar -xzf rulehawk-free-*-linux-amd64.tar.gz
./rulehawk
# dashboard on http://127.0.0.1:8426 — load your first config
Honest limits
- It reads configuration, not traffic. It can tell you a rule is unreachable; it cannot tell you whether a reachable rule is needed. That still requires flow data and a conversation with whoever asked for it.
- Four platforms. Others are not supported.
- "Over-permissive" is a judgement against a threshold you set, not a fact. Expect to tune it before the output is useful.
- It will not delete anything. Output is a report; the change is yours, through your change process.
If you are moving between vendors rather than cleaning up in place, that is a different job — see firewall migration after Expedition's end of life.