opnsense-pfsense
Administer OPNsense and pfSense firewalls: pf rules, VPNs, CARP failover, upgrades, and SSH diagnostics.
SKILL.mdskills/opnsense-pfsense/SKILL.md
GitHubOPNsense and pfSense Management
Manage, troubleshoot, and harden OPNsense and pfSense firewalls via SSH. Both are FreeBSD-based, pf-powered firewall distributions - most concepts, commands, and patterns apply to both.
Target versions (October 2026):
- OPNsense CE: 26.7.5 (current Community Edition update, released 2026-09-30, "Xenial Xenops"). Business Edition remains a separate even-quarter lane - do not quote the BE number as the CE version
- pfSense CE: 2.9.0 / pfSense Plus: 26.07
- CrowdSec: v1.8.1
Check OPNsense release notes and the pfSense version matrix before selecting a firmware image; CE, Plus, and Business Edition have separate patch lanes.
When to use
- Managing or troubleshooting OPNsense and pfSense firewalls over SSH
- Reviewing pf rules, NAT, CARP, Unbound, WireGuard, CrowdSec, or pfBlockerNG on these appliances
- Configuring VLANs, CARP HA failover, or interface assignments on firewall appliances
- Debugging connectivity between networks or VLANs routed through OPNsense/pfSense
- Hardening BSD firewall appliances and validating safe remote-change workflows
When NOT to use
- Linux networking, reverse proxies, VPN setup, or nftables work outside firewall appliances - use networking
- Cloud firewall rules, WAF configuration, or AWS/GCP/Azure network ACLs - use networking or the relevant IaC skill (terraform, ansible)
- General shell scripting or local shell behavior outside the BSD firewall context - use shell-scripting
- Fleet-wide configuration management via playbooks - use ansible
- Offensive testing, exploitation, or post-exploitation - use privilege-escalation
- Application-level security review or dependency scanning - use security-audit
AI Self-Check
Before returning any firewall commands, verify:
- Platform and version confirmed (OPNsense vs pfSense, appliance and plugin versions) - commands differ between them
- No commands that could lock out SSH or management access; rollback path stated
- Config backup taken (or reminded) before destructive changes
-
pfctlrules tested with-n(dry run) before applying - Service names correct for the target platform (
configctlvsservice) - Plugin names use correct prefix (
os-*for OPNsense, unprefixed for pfSense) - CARP changes target the master node, not the backup
- Shell syntax is POSIX sh (heredoc), not bash/zsh (csh/tcsh is the default shell on both)
- No firmware or plugin updates without explicit user confirmation
- Blast radius stated for any change affecting network connectivity
- DNS impact considered - changes to Unbound, DHCP, or firewall rules on port 53 can break name resolution for all clients on affected VLANs
- CrowdSec/pfBlockerNG checked when diagnosing blocks - bans look identical to firewall drops from the client side
- VLAN interface assigned before adding rules - unassigned VLANs pass no traffic through the firewall even if the trunk is tagged correctly
- Cross-cutting agent hygiene applied - see
references/agent-hygiene.md
Performance
- Prefer rule ordering that rejects high-volume unwanted traffic early and keeps expensive inspection scoped.
- Use aliases/tables for large address sets instead of expanding repetitive rules.
- Check state table, DNSBL, IDS/IPS, and plugin load before blaming WAN latency.
Best Practices
- Make HA changes one node at a time and verify CARP state before touching the peer.
- Keep emergency console or out-of-band access available for management-plane changes.
Workflow
For any change (skip for read-only diagnostics), copy this checklist and track progress:
- [ ] Step 1: Platform and target device confirmed
- [ ] Step 2: Config backup taken and copied off-box
- [ ] Step 3: Change dry-run (`pfctl -n`), blast radius stated, then applied
- [ ] Step 4: Verified (on failure: revert, confirm healthy, return to Step 3)
Step 1: Detect platform
If the platform is not obvious from context, ask the user which one they're running before issuing commands. Identify the target device explicitly - never assume which firewall you're talking to. Key differences at a glance:
| OPNsense | pfSense | |
|---|---|---|
| Base OS | FreeBSD (migrated from HardenedBSD in 2021) | FreeBSD |
| Config path | /conf/config.xml |
/cf/conf/config.xml |
| Service control | configctl service restart <svc> |
pfSsh.php playback svc restart <svc> or service <svc> restart |
| Plugin prefix | os-<name> (e.g., os-wireguard) |
No prefix (e.g., pfSense-pkg-WireGuard) |
| PHP shell | N/A | pfSsh.php (interactive PHP shell) |
| Quick rule add | N/A | easyrule pass wan tcp <src> <dst> <port> |
| IP blocking | CrowdSec (os-crowdsec) |
pfBlockerNG |
| Root shell | csh |
tcsh (same heredoc workaround applies) |
| IDS/IPS config | /tmp/suricata_*.log, eve.json |
/var/log/suricata/suricata.log, eve.json |
| Firmware CLI | configctl firmware check/status |
pkg-static update + GUI |
| Template engine | configd + configctl template |
PHP-generated configs |
| REST API | Yes (/api/, key/secret auth) |
Yes (similar, different endpoints) |
| Licensing | Free, open source | CE: free but slower updates; Plus: $129/yr on non-Netgate HW |
| Release cadence | Bi-weekly, fixed schedule | Irregular, Netgate hardware prioritized |
2026 status: OPNsense is the clear choice for new deployments. pfSense CE gets slower updates and zero priority; Plus costs $129/year on non-Netgate hardware. Migration from pfSense to OPNsense has no automated path - expect ~60% clean config transfer, manual rebuild for NAT rules, VPN, and DNS forwarder settings.
When platform is unknown, these commands work on both:
pfctl -sr # firewall rules
pfctl -ss # state table
ifconfig # interfaces
pkg info # installed packages
netstat -rn # routing table
sockstat -4l # listening sockets
Step 2: Back up config
Before any change that modifies rules, services, plugins, or firmware:
- Default over SSH: copy the config file to a timestamped name, then pull that copy off-box
with
scp. OPNsense:cp /conf/config.xml /root/config-`date +%Y%m%d-%H%M%S`.xml. pfSense: same with/cf/conf/config.xml - Alternatives: GUI export (OPNsense System > Configuration > Backups, pfSense Diagnostics >
Backup & Restore) or the OPNsense
/api/core/backup/download/thisendpoint. There is noconfigctlaction that exports the config - For major upgrades on virtualized firewalls, pair config backup with a hypervisor snapshot
Skip this step only for read-only operations (diagnostics, log review, status checks).
Step 3: Execute the task
Apply changes using the platform-appropriate commands. Refer to the domain sections below and the reference files for specifics. For any change that affects connectivity:
- Test
pfctlrules with-n(dry run) before applying - State the blast radius ("this will drop all VPN tunnels for ~30s")
- On HA pairs, always change on the master node and let XMLRPC sync propagate
Step 4: Verify
After every change, confirm the firewall is healthy:
- Connectivity: can you still reach the device? Can clients reach the internet?
- Logs: check
/var/log/filter.log, service logs, and CrowdSec/Suricata if active - Service status:
service -e(works on both, FreeBSD base) pluspluginctl -s <name> statusfor OPNsense plugin services.configctlwith no arguments lists the available configd actions; there is noconfigctl service list - State table:
pfctl -si | grep entries- watch for unexpected drops or state exhaustion
If any check fails, revert the change (or restore the Step 2 backup), confirm the device is healthy again, then return to Step 3 with a corrected change.
Quick Task Procedures
Creating a firewall rule (VLAN to server)
- Identify the interface the traffic originates from (e.g.,
opt1for VLAN 50) - Confirm the VLAN interface is assigned:
ifconfigmust show the VLAN interface UP. If the VLAN is not assigned to an OPNsense/pfSense interface yet (Interfaces > Assignments), it cannot have rules - assign it first. - Check existing rules:
pfctl -sr | grep <iface>- new interfaces have no rules (implicit deny all). Confirm the baseline before adding anything so you know exactly what you're changing. - Create aliases for source subnet and destination server (keeps rules readable):
- OPNsense API (the config file holds credentials; no secret appears in argv or shell history):
: "${FW_API_CURL_CONFIG:?set to a mode-0600 curl config populated from an approved secret store or no-echo prompt}" curl --config "$FW_API_CURL_CONFIG" -H 'Content-Type: application/json' -X POST https://<fw>/api/firewall/alias/add_item \ -d '{"alias":{"name":"SourceVLAN","type":"network","content":"10.0.50.0/24"}}' curl --config "$FW_API_CURL_CONFIG" -H 'Content-Type: application/json' -X POST https://<fw>/api/firewall/alias/add_item \ -d '{"alias":{"name":"WebServer","type":"host","content":"10.0.1.100"}}' - OPNsense CLI:
configctl template reload OPNsense/Filter(after editing alias via API or XML). To verify the alias was created:configctl template list | grep Alias, then confirm withpfctl -t WebServer -T show. - pfSense:
easyruledoesn't support aliases - use GUI or edit/cf/conf/config.xmldirectly - Verify both aliases:
pfctl -t SourceVLAN -T show && pfctl -t WebServer -T show.
- OPNsense API (the config file holds credentials; no secret appears in argv or shell history):
- Add a pass rule on the VLAN interface (not WAN - pf evaluates rules on the interface where traffic enters): source = SourceVLAN, destination = WebServer, port = 443
- Place the allow rule above any block-all rule for that interface (OPNsense/pfSense interface rules use
quickby default, so the FIRST matching rule wins - put the specific allow above the broad block) - Test:
pfctl -n -f /tmp/rules.debug(OPNsense) to dry-run before applying - Apply:
configctl filter reload(OPNsense) orpfSsh.php playback svc restart filter(pfSense) - Verify:
pfctl -sr | grep <alias>to confirm the rule is active
Creating a block rule (isolate IoT VLAN)
Block IoT devices from reaching internal networks while allowing internet access:
- Identify the IoT VLAN interface (e.g.,
opt3for VLAN 30) - Create an alias for RFC1918 ranges: name
RFC1918, typeNetwork, content10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 - Allow only required infrastructure exceptions first: for example, TCP/UDP 53 to the exact intended resolver, including a resolver on the firewall's private address. Account for DHCP if used.
- Below those exceptions, block IoT traffic to
RFC1918, then pass only the intended internet services such as TCP 443. Leave other traffic denied. - With
quickrules, the FIRST match wins: narrow infrastructure exceptions, private-network block, then internet allow. RFC1918 covers only IPv4; define corresponding IPv6 isolation or disable IPv6 on this VLAN deliberately. - Test and apply as above:
pfctl -n -f /tmp/rules.debug, thenconfigctl filter reload
Troubleshooting connectivity after VLAN changes
Work through these steps in order. Do not skip ahead or assume the root cause - each step eliminates one layer. The most common failure is a missing outbound NAT rule, not a firewall rule. Also check CrowdSec (cscli decisions list) and Suricata early - their blocks look identical to firewall drops from the client side.
Prerequisite: the VLAN must be assigned to a firewall interface before it can have rules, DHCP, or NAT. In OPNsense: Interfaces > Assignments > add the VLAN, then enable it and set its IP. In pfSense: Interfaces > Interface Assignments. An unassigned VLAN passes no traffic through the firewall even if the parent trunk is tagged correctly.
- Interface assigned and UP?
ifconfig- is the VLAN interface listed and UP? If not: assign it (see prerequisite above). If listed but DOWN: enable it in the GUI or check the parent interface. - Blocklist check:
cscli decisions list(OPNsense CrowdSec) or check pfBlockerNG deny logs (pfSense) - CrowdSec and pfBlockerNG bans look identical to firewall drops from the client side. Clear false positives before digging into rules. - Services running?
service -eon either platform, pluspluginctl -s <name> statusfor OPNsense plugin services - confirm DHCP, DNS (Unbound), and the packet filter are running. A stopped DHCP server on the new VLAN means clients never get an IP. - Rules present?
pfctl -sr- any pass rules on the new VLAN interface? New interfaces have no rules by default (deny all). - NAT configured? Check outbound NAT rules include the new VLAN subnet. On OPNsense: Firewall > NAT > Outbound. Missing outbound NAT is the #1 cause of "VLAN can't reach internet."
- DNS working?
drill google.com @<firewall-ip>from a VLAN client. If this fails while public-IP connectivity works, inspect the DNS path: resolver service, TCP/UDP 53 rules and the reply path. IP reachability alone does not rule out a DNS-specific firewall block. - Packet capture:
tcpdump -ni <vlan-iface> host <client-ip>- are packets arriving at the firewall?- Reading tcpdump output: correlate ingress requests with egress traffic, translated addresses and return packets. Requests without replies on one interface do not identify the cause: inspect rule counters/states, routes, NAT, upstream reachability and the target's return path. Use
-vfor header details; capture payloads only when needed and authorized.
- Reading tcpdump output: correlate ingress requests with egress traffic, translated addresses and return packets. Requests without replies on one interface do not identify the cause: inspect rule counters/states, routes, NAT, upstream reachability and the target's return path. Use
- If no packets are captured, first confirm the client generated traffic and the interface/filter are correct, then inspect VLAN tagging, trunks and switch ports. If requests leave the WAN but replies do not return, investigate the upstream/target path before changing local rules.
FreeBSD Mental Model
Read references/platform-and-operations.md for the detailed FreeBSD shell model, key commands,
config system, REST API, IPv6 gotchas, SOPs, and recovery procedures.
- Treat both platforms as FreeBSD appliances, not Linux hosts.
- For anything beyond trivial SSH one-liners, prefer piping a POSIX
shheredoc instead of fightingcshortcsh. - Guard non-zero informational commands with
; truewhen running checks in parallel.
Operations and Common Tasks
- Check plugin or package layers early because they often explain traffic behavior that looks like a firewall-rule problem.
- Use
references/plugins.mdfor plugin specifics andreferences/hardening.mdfor hardening and CARP guidance.
Reference Files
references/platform-and-operations.md- FreeBSD shell model, key commands, config system, REST API, IPv6 gotchas, SOPs, and recovery procedures (both platforms)references/plugins.md- operational guidance for common OPNsense plugins (CrowdSec, WireGuard, Suricata, HAProxy, ACME, FRR, etc.). For pfSense package equivalents, map concepts using the platform comparison table above.references/hardening.md- comprehensive hardening checklist. OPNsense-focused but most items apply to pfSense with equivalent settings in its GUI/config.
Output Contract
See references/output-contract.md for the full contract.
- Skill name: OPNSENSE-PFSENSE
- Deliverable bucket:
audits - Mode: conditional. When invoked to analyze, review, audit, or improve existing repo content, apply the reporting size and evidence rules in
references/output-contract.mdand write the deliverable todocs/local/audits/opnsense-pfsense/<YYYY-MM-DD>-<slug>.md. When invoked to answer a question, teach a concept, build a new artifact, or generate content, respond freely without the contract. - Severity scale:
P0 | P1 | P2 | P3 | info(see shared contract; only used in audit/review mode).
Related Skills
- networking - for Linux reverse proxies, VPNs, DNS, nftables, and cloud network ACLs (AWS/GCP/Azure security groups, WAF rules) outside BSD firewall appliances
- terraform - for provisioning and managing cloud firewall rules, WAF policies, and network ACLs as infrastructure-as-code
- ansible - for fleet-wide firewall automation or playbook-based configuration management
- shell-scripting - for general shell scripting and local shell behavior; this skill covers the FreeBSD firewall context
- security-audit - for defensive security review of application code and supply chain, rather than firewall administration
- privilege-escalation - for authorized offensive testing and post-exploitation, not defensive firewall operations
Rules
These exist because bricking a firewall remotely means driving to wherever it is.
- Never modify rules that could lock out SSH access. If the change touches the SSH port or the management interface, triple-check the rule order and confirm with the user.
- Never disable the LAN interface or change its IP without explicit confirmation and a rollback plan.
- Never apply firmware or plugin updates without asking first - updates can reboot the device and may require physical console access if something goes wrong.
- Always confirm destructive changes: rule deletions, service disables, plugin removals,
state table flushes (
pfctl -Fa). - OPNsense CrowdSec: don't delete decisions or bouncers without understanding why they exist. A ban that looks wrong might be catching a real attack.
- pfSense pfBlockerNG: don't disable feed lists without understanding what they block. Review the deny logs before removing feeds.
- HA/CARP (both platforms): never make config changes directly on the backup node - XMLRPC sync from master will overwrite them. Always change on master and let sync propagate.
- pfSense
easyrule: convenient but creates rules without descriptions. Document what you added and why. Consider using the GUI or config.xml for permanent rules instead.