Hexward Labs

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.

Written by a network security engineer · last reviewed 20 August 2026

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:

PlatformEvaluationAudit implication
iptables / nftablesFirst match wins within a chainAlso follow jumps between chains — shadowing can happen across a jump, not just within one list.
Cisco ASAFirst match wins per ACLCheck which ACL is actually bound with access-group. Unbound ACLs are dead in a different way.
FortiGateFirst match winsPolicies are per-VDOM and per interface pair; compare only within the same scope.
pfSense / OPNsense (pf)Last match wins — unless the rule is marked quickThe 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:

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:

  1. Export the running config, not the documentation. They diverge.
  2. Expand every object and group to literals. Shadowing is invisible while rules refer to named groups whose contents overlap.
  3. Compare each rule against every rule above it — source set, destination set, protocol, port set. Full containment above means dead below.
  4. Check hit counters where the platform has them (show access-list on 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.
  5. 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
Source on GitHub Other Hexward tools

Honest limits

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.