OPNsenseXI · Firewall StatesFirewall state operations
Rule changes do not affect existing state — the trap that wastes hours
What you'll learn
- Explain why a rule change does not affect flows that already have state
- Recognise the symptoms of an existing flow continuing to match an old rule
- Apply the verification discipline that catches the trap before users do
- Clear state deliberately to apply a new rule to existing flows
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
An operator edits a firewall rule. The GUI shows the change. The Apply completes. The next packet — and every packet after — should match the new rule. But a long-running TCP session continues to behave as if the rule were unchanged. The operator checks the GUI; the rule is correct. The operator checks the firewall log; the session is logging against the old rule number. The operator spends an hour convinced the change did not apply, when in fact it did — for every new flow. The existing flow matches the state that was created when the rule was different.
This trap wastes hours in production environments. The fix is to understand why it happens and to recognise the symptoms quickly.
Why rule changes do not affect existing state
PF state records the decision that was made when the flow first matched a rule. When a packet arrives, PF checks the state table first. If a state matches, PF allows the packet and increments the counters. The ruleset is not consulted.
The state entry does not store the rule number or a pointer to the rule. It stores the match criteria and the action. The action — pass, block, match — is what the rule decided when the flow started. Subsequent packets follow that decision without re-evaluation.
The implication: a rule change that permits or blocks traffic differently has no effect on existing flows. The flow continues to match its state, which was created under the old rule. New flows match the new rule; old flows match the old rule until their state expires or is cleared.
$ pfctl -s state | grep 192.0.2.50all tcp 192.0.2.50:51820 <- 203.0.113.50:443 ESTABLISHED:ESTABLISHED
all tcp 192.0.2.50:51820 -> 203.0.113.50:443 ESTABLISHED:ESTABLISHEDIllustrative output
The trap in practice
The trap has three steps:
- Operator edits a rule. The GUI shows the new rule. The Apply completes.
- Existing flows continue to match the old rule. Long-running TCP sessions, persistent VPN tunnels, ongoing database connections — all continue to behave as before.
- Operator suspects the change did not apply. The operator re-checks the GUI, the compiled ruleset, the rule number in the log.
The waste of time is the operational cost. The trap is harmless if recognised quickly; it is expensive if the operator does not know it exists.
Recognising the trap
The symptoms are distinctive:
- A new rule is in the GUI. The operator confirmed it; the compiled ruleset shows it.
- Existing flows behave as if the rule were unchanged. A blocked flow is still passing; an allowed flow is still blocked.
- New flows behave according to the new rule. A new connection from the same source matches the new rule correctly.
The combination of “new flows correct, old flows wrong” is the signature of the trap.
Verifying with the state table
The fastest way to confirm the trap is to look at the state table:
pfctl -s state | grep 192.0.2.50
If the state shows entries for the source, the firewall has the flow in memory. The flow matches the state, not the rules. The new rule does not apply.
The next step is to identify the affected flows precisely and decide whether to clear them.
Verifying the new rule applies to new flows
To confirm the new rule applies correctly to new flows, send a test packet that creates a new flow and verify the firewall’s decision:
# From a host that matches the new rule's criteria:
curl https://example.com/ # new TCP flow
Then check the firewall log. The new flow should match the new rule number.
The fix: clear the affected state
To apply the new rule to existing flows, clear the state:
# Identify affected flows
pfctl -s state | grep <criteria>
# Clear targeted
pfctl -k <ip>
# or
pfctl -k <src> -k <dst>
# or (with a labelled rule)
pfctl -k label -k <label>
The next packet of the cleared flow is evaluated against the new rule.
The discipline: target the clearing to the affected flows. Do not blanket-flush the entire state table to apply a single rule change.
When the trap is desirable
The state-caching behaviour is desirable in most production changes:
- A rule permit becomes a rule block. The operator blocks traffic that was previously allowed. Without state caching, every existing flow would be terminated by the new rule.
- A rule block becomes a rule permit. The operator allows traffic that was previously blocked.
- A rule is reordered. The operator moves a more specific rule above a general rule.
The trap is the cost of a behaviour that is usually desirable. The operator’s job is to recognise when the cost is too high and to clear state deliberately.
The change-control discipline
The discipline that prevents the trap from wasting hours has three parts:
Plan for state clearing in the change record
Every rule change record should answer: “Will existing flows need to be cleared to apply this change?” If yes, the change record identifies the flows and the clearing command.
Verify the rule change with a test packet
After applying a rule change, send a test packet that creates a new flow. Verify the firewall log shows the new rule matched. This confirms the rule applied correctly to new flows.
Clear state with a documented action
If existing flows must match the new rule, clear the state deliberately:
- Document the clearing action.
- Run
pfctl -kwith a targeted filter. - Verify the affected flows re-establish (or are dropped) according to the new rule.
The discipline turns the trap into a controlled action.
$ pfctl -k 192.0.2.50 && pfctl -s state | grep 192.0.2.50 || echo 'no state for 192.0.2.50'3 states killed
no state for 192.0.2.50Illustrative output
The trap in HA environments
In a high-availability pair, the trap has an additional wrinkle. State is synchronised between the nodes via pfsync. A state created on the MASTER is replicated to the BACKUP. A rule change applied on the MASTER takes effect for new flows; existing flows continue on both nodes with the old state.
When clearing state to apply a rule change:
- Clear the state on the MASTER. The pfsync replication will propagate the clearing to the BACKUP.
- Verify both nodes are clear.
If the BACKUP becomes MASTER during a state-clearing action (failover), the new MASTER has the cleared state.
Summary
- PF state records the rule decision made at flow creation. Subsequent packets match the state, not the rules.
- A rule change applies to new flows immediately; existing flows continue to match their state until the state expires or is cleared.
- The signature of the trap: new flows behave per the new rule; existing flows behave per the old rule.
- The fix: clear state deliberately with
pfctl -kfor targeted flows, or wait for natural expiry. - State caching is the right default for production stability; the trap is the cost.
- Plan for state clearing in the change record; verify with a test packet; clear with documentation.
Knowledge check · 4 questions
Q1. You block a service in OPNsense. New flows to the service are blocked correctly. An existing connection to the service continues to work. What is the most likely explanation?
Q2. State caching means a rule change does not affect any flow until the state table is flushed or the firewall is rebooted.
Q3. Which of the following are symptoms that indicate state caching is causing an apparent rule change failure? Select all that apply.
Q4. You need to apply a rule change to all existing flows that match the old rule. Which is the most appropriate action?
Passing score: 75%. Answers are checked in this browser.