Skip to main content
RunBook Academy

← All labs in OPNsense

Lab · intermediate · ~60 min

Lab: Build a baseline LAN/WAN firewall ruleset

B · Nested virtualisationC · Simulation

Objectives

  • Replace the wizard's default-allow LAN rule with a deliberate allow list
  • Add a default-deny WAN inbound rule that logs
  • Use pfctl to verify the runtime ruleset matches the GUI
  • Reproduce a blocked flow and find it in the firewall log

Prerequisites

This lab replaces the OPNsense default ruleset with a deliberate, auditable baseline. The post-wizard ruleset (“allow everything from LAN, block everything from WAN”) is functional, but it is not a policy. A policy says what is permitted, who is permitted to do it, and how the rest is handled. This lab implements that policy and verifies the runtime ruleset with pfctl so the operator can prove the GUI matches what the kernel is enforcing.

The lab also introduces the most important habit in firewall operations: route the verification through the same tools the attacker would use, not the GUI. The GUI is the input; pfctl is the truth.

Objective

By the end of this lab you can:

  • Read the live PF ruleset with pfctl -sr.
  • Replace the wizard’s default-allow LAN rule with a service-by-service allow list.
  • Add a default-deny WAN inbound rule with logging.
  • Trigger a block and locate it in the firewall log.
  • Reload OPNsense safely and confirm the on-disk ruleset matches the runtime.

Requirements

  • A working OPNsense instance from the previous lab (the baseline backup is the rollback target).
  • A LAN-side workstation with curl, dig, and ping.
  • Web UI access to the firewall from the LAN side.
  • Optional: a workstation on the WAN side or a second NIC for testing the inbound deny.

Tasks

Task 1: Capture the current ruleset

Before any change, capture what is running. The GUI is the input; pfctl -sr is the truth:

configctl filter show rules | tee /tmp/opnsense-rules-before.txt
pfctl -sr | tee /tmp/opnsense-pf-before.txt

The two views should agree. The first is the OPNsense model (filter rules); the second is the compiled PF ruleset the kernel is enforcing. If they differ, the GUI is showing a staged change that has not been applied, or a service has injected a rule the model does not know about.

Count the rules:

pfctl -sr | wc -l

The post-wizard ruleset is small — fewer than twenty rules. Note the count; the comparison after the change is the evidence that the ruleset changed.

Task 2: Disable the LAN default-allow rule

The wizard adds an IPv4 LAN net to any rule on LAN that allows everything. The rule is the reason a freshly installed OPNsense passes the “does the Internet work?” smoke test from the LAN. It is also the reason a compromised LAN host can exfiltrate on any port.

In the GUI, Firewall → Rules → LAN, edit the default rule and disable it (the green enable toggle on the right). Do not delete it yet — the rollback path is the green toggle back. Click Apply changes.

Re-check the runtime:

pfctl -sr | grep -E 'LAN|em1'

The default-allow rule should still be in the model but no longer in the compiled ruleset. The discrepancy is the warning sign: the GUI is showing the model, not the kernel view. The production check is pfctl -sr.

Task 3: Add the deliberate outbound allow list

Add four rules on the LAN interface, in order, top down:

  1. Allow LAN to WAN: DNS (UDP 53). Without this, name resolution fails and the “the Internet is down” page arrives without DNS working.
  2. Allow LAN to WAN: NTP (UDP 123). Hosts need time sync to validate TLS.
  3. Allow LAN to WAN: HTTPS (TCP 443). The majority of modern traffic.
  4. Allow LAN to WAN: ICMP echo (ping). For diagnostics.

For each rule:

  • Action: Pass
  • Interface: LAN
  • Protocol: as listed
  • Source: LAN net
  • Destination: any
  • Log: enabled

The log flag is critical. Without it the lab cannot prove the rules worked — only that traffic passed.

Task 4: Add the default-deny rule at the bottom

Below the four allow rules on LAN, add:

  • Action: Block
  • Interface: LAN
  • Direction: in
  • Protocol: any
  • Source: any
  • Destination: any
  • Log: enabled

This rule is the policy: anything the four allow rules do not cover is blocked and logged. Apply.

Task 5: Add the WAN-side default-deny with logging

On the WAN interface, the wizard already adds one IPv4 * to * block rule, but it is silent. Replace or edit it so logging is enabled. The verbosity should be the default, not “full packet” — full-packet logs grow quickly and obscure the signals.

In the GUI: Firewall → Rules → WAN, edit the block rule (or add a new one at the bottom of the list) with logging enabled. Apply.

Verify:

pfctl -sr | grep -E 'block|deny'

Expect a block (or deny) rule on WAN that names the interface and logs.

Task 6: Reproduce a block and find it in the log

From the LAN workstation, try something that should be blocked:

# SMTP outbound - deliberately not in the allow list
curl -v --connect-timeout 5 telnet://smtp.example.com:25

Expect a connection timeout. Then from the firewall:

# Most recent firewall log entries
configctl filter show log | tail -20
# Or via the GUI: Firewall → Log Files → Live View

The block should be visible with the source IP, destination IP and port, and the rule name. The rule name is the auditor’s proof — a rule named Default deny LAN to any makes the intent unambiguous.

Task 7: Verify the live ruleset with packet capture

pfctl -sr is the contract. One more check — confirm the rules actually fire:

# On the LAN side, start a TCP connection that the rules SHOULD allow
tcpdump -ni em1 'tcp port 443' -c 2 &

# From the LAN workstation
curl -sI https://example.com

The tcpdump output should show the SYN, SYN-ACK, and ACK trio for the connection. If the SYN appears but the SYN-ACK does not, the firewall passed the SYN outbound but is blocking the return — a state-tracking problem, not a rule problem.

Task 8: Reload cleanly and confirm

Apply the ruleset, then verify the kernel view:

configctl filter reload
pfctl -sr | wc -l

The new count should be larger than the pre-change count by the number of rules added (four LAN allows + one LAN deny + log edits on WAN = ~5–7 additional rules, depending on whether the wizard add was replaced or extended).

Validation

  • pfctl -sr shows the four explicit LAN allow rules followed by the default deny.
  • pfctl -sr shows a logging-enabled block rule on WAN.
  • The SMTP connection attempt in Task 6 produces a log entry under the deny rule.
  • HTTPS from LAN to a public host works and shows traffic in the LAN-side tcpdump capture.
  • The rule count increased by the expected number of rules.

Expected Result

You have a baseline ruleset that follows the discipline “allow what is needed, log what is blocked, deny the rest, and verify the runtime against the model.” The wizard’s default-allow rule is gone. The LAN workstation can reach the Internet for DNS, NTP, HTTPS, and ping; anything else is blocked and logged.

Troubleshooting

  • All traffic from LAN is blocked. The four allow rules did not apply. Check pfctl -sr | grep LAN — the entries should be there. If they are not, the GUI’s Apply did not commit. Repeat the Apply and reload.
  • Logging shows nothing on the block. The log flag is not set on the rule. Edit the rule, enable “Log packets that are handled by this rule”, save, Apply.
  • HTTPS works from the LAN but the connection log is empty. The TCP fast-open or HTTP/3 path is masking the log. Try a plain curl --http1.1 to force the TCP handshake through the firewall.
  • pfctl -sr shows rules that are not in the GUI. A plugin (Suricata, Zenarmor) is injecting its own rules. These are normal, but document them — they are part of the effective policy.

Cleanup

The baseline ruleset is the new starting state for the course. Snapshot it before the next lab:

configctl backup download
# Save as opnsense-baseline-rules.xml

To roll back to the post-wizard ruleset:

# Substitute your own values before running:
# Basename of the backup taken at the end of the previous lab;
# list the candidates with: ls -1 /conf/backup/
PREVIOUS_BASELINE=config-2026-08-13T18:22:41

# Restore the backup from the previous lab
configctl restore "/conf/backup/$PREVIOUS_BASELINE.xml"
# Or in the GUI: System → Configuration → Backups → Restore

What you learned

  • The GUI is the input; pfctl -sr is the truth. Verify the runtime against the model.
  • A default-allow LAN rule is convenient but not a policy. The first change in any production deployment is replacing it with a default-deny plus an explicit allow list.
  • Logging is part of the rule, not an afterthought. A rule without log is invisible to the next operator.
  • The four-service allow list (DNS, NTP, HTTPS, ICMP) is the minimum a modern LAN needs. Anything else is candidate for explicit allow or an explicit deny-and-log.

Deliverables

  • · A working LAN-outbound ruleset (DNS, NTP, HTTPS, ICMP)
  • · A default-deny WAN inbound rule with logging enabled
  • · A live capture from the firewall log showing the deny
  • · A diff between the post-wizard ruleset and the new ruleset

Verification status

Last reviewed
2026-08-14
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.