Skip to main content
RunBook Academy

OPNsenseIII · Stateful Firewalling and PFStateful firewalling and PF

Quick and non-quick rules

Intermediate⏱ ~12 minpfctl

What you'll learn

  • Explain the quick modifier and its effect on PF evaluation
  • Describe when last-match-wins is the right semantic
  • Identify the places OPNsense uses non-quick rules internally
  • Recognise the production traps of last-match-wins rulesets

Prerequisites

Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14

Not yet marked complete on this device.

The quick keyword is one of the most powerful and most confusing features in PF. It changes the rule-evaluation model from first-match-wins to first-AND-last-match-wins: rules without quick keep being evaluated, and the last matching rule decides. OPNsense uses quick everywhere by default, but there are specific places — anchor rules, certain floating rules, and the implicit default block — where non-quick semantics are exactly what is needed.

This lesson covers how quick and non-quick rules behave, where OPNsense uses each, and the production traps that follow from mixing them.

The default: first match wins with quick

OPNsense generates every GUI rule with quick set. When a packet arrives, PF walks the ruleset top to bottom. The first rule whose match criteria match the packet is the one that decides — evaluation stops.

@87 pass in quick on igb0 inet proto tcp from 192.0.2.0/24 to any port = https
@88 pass in quick on igb0 inet proto tcp from 192.0.2.0/24 to any port = http
@100 block drop in quick on igb0 inet from 10.0.0.0/8 to any

If a packet from 192.0.2.50 to 203.0.113.50:443 arrives, rule @87 matches first and evaluation stops. PF permits the packet. The block at @100 never runs, even though the packet’s destination happens to be in the 10.0.0.0/8 block.

This is the behaviour most operators expect. quick is default.

Without quick: last match wins

If a rule does not have quick, PF continues evaluating after the match. Every subsequent rule that matches also runs. The last matching rule is the one whose action is applied.

@87 pass in on igb0 inet proto tcp from 192.0.2.0/24 to any port = https
@88 pass in on igb0 inet proto tcp from 192.0.2.0/24 to any port = http
@100 block drop in on igb0 inet from 10.0.0.0/8 to any
@200 block drop in on igb0 inet from any to any

For the same packet, rule @87 matches and PF notes “pass”, then rule @100 does not match (the source is not in 10.0.0.0/8), then rule @200 matches and PF notes “block drop”. The last matching rule wins: the packet is blocked.

This is rarely what an operator wants, because it is hard to reason about. Non-quick rules are an advanced feature.

Where OPNsense uses non-quick rules

Three places in the generated ruleset use non-quick rules deliberately.

The default block at the bottom

OPNsense’s compiled ruleset ends with rules that block unmatched traffic. These rules do not have quick. The reason: they are intended as the default — they should only apply if no other rule has matched. By being non-quick, they only matter if nothing earlier matched.

For a packet that matches no earlier rule, the implicit block at the bottom matches (because it matches everything), and being the last match, its action is applied. The packet is blocked.

Anchor rules

Anchors let you load sub-rulesets at specific points in the main ruleset. OPNsense uses anchors for several features (openVPN per-rule, IPsec per-rule, captive portal). The anchor rule itself is typically non-quick: the main ruleset should continue evaluating after the anchor runs.

Floating rules with careful ordering

A floating rule (a rule that applies to multiple interfaces) can be non-quick when the operator wants the rule to enhance the evaluation rather than replace it. For example, a floating log rule at the bottom that logs everything but does not decide.

Verifying quick in the compiled ruleset

To verify which rules have quick and which do not:

Read-only / Safepfctl -s rules — quick visibility
$ pfctl -s rules | head -25
@87 pass in quick on igb0 inet proto tcp from 192.0.2.0/24 to any port = https
@88 pass in quick on igb0 inet proto tcp from 192.0.2.0/24 to any port = http
@89 pass in quick on igb0 inet proto tcp from 192.0.2.0/24 to any port = 53
@92 pass in quick on igb0 inet proto tcp from 192.0.2.0/24 to port = 22
@100 block drop in quick on igb0 inet from 10.0.0.0/8 to any
@110 block drop in on igb1 inet from any to any
@115 pass out all

Illustrative output

The pattern to remember:

  • A rule with quick and pass permits matching traffic and stops evaluation.
  • A rule with quick and block blocks matching traffic and stops evaluation.
  • A rule without quick and pass permits but continues.
  • A rule without quick and block blocks but continues.

For any packet, the action of the last matching rule applies. If no rule matches, the implicit default block applies.

Production traps

Three production patterns cause incidents:

  1. A pass rule without quick is silently overridden. A non-quick pass rule below a more general block can be shadowed by the block. The operator adds a permit, the traffic is still blocked. The fix is to verify with pfctl -s rules and add quick if missing.
  2. A block rule without quick does not block. A non-quick block rule above a more general pass is shadowed by the pass. The operator adds a block, the traffic is still permitted. Same fix.
  3. Mixing quick and non-quick without testing. A ruleset that mixes both behaves non-intuitively for new operators. Standardise on quick (the OPNsense default) unless you have a specific reason not to.

Summary

  • quick stops evaluation on the first match. OPNsense uses quick on every GUI rule.
  • Without quick, the last matching rule decides.
  • The implicit default block is non-quick by design — it only applies if no other rule matched.
  • Anchor rules are typically non-quick to allow evaluation to continue after the anchor runs.
  • Non-quick rules are an advanced feature. Use them deliberately and verify with pfctl -s rules.

Knowledge check · 4 questions

  1. Q1. OPNsense compiles a GUI rule to: pass in on igb0 inet proto tcp from 192.0.2.0/24 to any port = 22. A second rule on the same interface is: block in on igb0 inet from 10.0.0.0/8 to any. Both are quick. A packet from 192.0.2.50:51820 to 10.0.0.1:22 arrives. What happens?

  2. Q2. A non-quick block rule placed above a non-quick pass rule in PF means the pass wins for matching traffic, because the last matching rule decides.

  3. Q3. Which of the following statements about quick and non-quick rules in OPNsense are correct? Select all that apply.

  4. Q4. You inject a custom pf.conf snippet via System → Advanced that adds a non-quick pass rule above the implicit default block. Traffic that should match this rule is being blocked. What is the most likely cause?

Passing score: 75%. Answers are checked in this browser.