OPNsense Firewall
This role manages firewall rules across multiple network interfaces on OPNsense via the REST API. It supports configuration for VLANs (VLAN10-22), WAN, LAN, and WireGuard interfaces.
OPNsense Firewall Role
Overview
This role manages firewall rules across multiple network interfaces on OPNsense via the REST API. It supports configuration for VLANs (VLAN10-22), WAN, LAN, and WireGuard interfaces. The role provides full lifecycle management: creating new rules, updating existing rules when configuration changes, and deleting orphaned rules that exist on the firewall but are no longer defined in the vars files.
Purpose
- Multi-Interface Management: Configure rules for 10+ interfaces
- Code as Configuration: Define firewall rules in YAML
- Centralized Control: Manage all firewall rules from Ansible
- API-Based: Reliable automation via OPNsense REST API
- Full Lifecycle Management: Create, update, and delete rules
- Idempotent: Safe to run multiple times using sequence-based idempotency
- Automatic Cleanup: Removes orphaned rules not defined in vars
- Network Segmentation: Enforce VLAN security policies
- Logging: Optional traffic logging per rule
Requirements
- Ansible 2.9 or higher
- OPNsense firewall with API access enabled
- API key and secret stored in Ansible Vault
- Network connectivity to OPNsense (VLAN10)
- OPNsense user with firewall rule permissions
What are Firewall Rules?
Firewall rules control network traffic by:
- Allowing or blocking packets
- Based on source, destination, protocol, port
- Processed in order (first match wins with quick rules)
- Applied per network interface
Rule example: “Allow HTTPS from VLAN12 to Internet”
Role Variables
Required Variables
| Variable | Required | Description |
|---|---|---|
vault_opnsense_bjoffrey_user_api_key | Yes | OPNsense API key (in vault) |
vault_opnsense_bjoffrey_user_api_secret | Yes | OPNsense API secret (in vault) |
opnsense_firewall_rules | Yes | Dictionary of rules by interface |
Optional Variables
| Variable | Default | Description |
|---|---|---|
opnsense_firewall_validate_certs | true | Validate SSL certificates |
opnsense_firewall_vlan10_rules | [] | VLAN10 rules (management) |
opnsense_firewall_vlan12_rules | [] | VLAN12 rules (servers) |
opnsense_firewall_vlan14_rules | [] | VLAN14 rules (desktops) |
opnsense_firewall_vlan16_rules | [] | VLAN16 rules (WiFi trusted) |
opnsense_firewall_vlan18_rules | [] | VLAN18 rules (WiFi guest) |
opnsense_firewall_vlan20_rules | [] | VLAN20 rules (WiFi CCTV) |
opnsense_firewall_vlan22_rules | [] | VLAN22 rules (ethernet guest) |
opnsense_firewall_wg0_rules | [] | WireGuard VPN rules |
opnsense_firewall_lan_rules | [] | LAN interface rules |
opnsense_firewall_wan_rules | [] | WAN interface rules |
Variable Details
opnsense_firewall_rules
Combined dictionary of all firewall rules organized by interface.
Structure:
opnsense_firewall_rules:
vlan10:
- rule1
- rule2
vlan12:
- rule1
wan:
- rule1
Typically constructed in playbook:
opnsense_firewall_rules:
vlan10: "{{ opnsense_firewall_vlan10_rules }}"
vlan12: "{{ opnsense_firewall_vlan12_rules }}"
wan: "{{ opnsense_firewall_wan_rules }}"
Rule Structure
Each rule in a list has these fields:
Required fields:
- sequence: "1" # Unique sequence number for idempotency
action: pass|block|reject
interface: vlan10|vlan12|lan|wan|wg0
description: "Rule description"
Optional fields:
enabled: "1" # Enable (1) or disable (0)
quick: "1" # Stop processing on match
direction: in|out # Traffic direction
ipprotocol: inet|inet6 # IPv4 or IPv6
protocol: tcp|udp|icmp # Protocol
source_net: any|alias|CIDR # Source address
source_port: port # Source port
source_not: "0" # Invert source match
destination_net: any|alias|CIDR # Destination
destination_port: port # Destination port
destination_not: "0" # Invert dest match
log: "1" # Log matched packets
statetype: keep|sloppy|synproxy|none # State tracking
categories: "CategoryName" # Assign rule to a category (by name)
Complete example:
opnsense_firewall_vlan12_rules:
- sequence: "1"
action: pass
interface: vlan12
description: "Allow VLAN12 to Internet HTTPS"
protocol: tcp
source_net: vlan12
destination_net: any
destination_port: "443"
log: "1"
categories: "Web"
- sequence: "2"
action: block
interface: vlan12
description: "Block VLAN12 to VLAN10"
source_net: vlan12
destination_net: vlan10
log: "1"
categories: "Block"
sequence
The sequence number is the key field for idempotency. It uniquely identifies a rule within an interface.
Purpose:
- Maps rules in vars to rules on the firewall
- Enables create/update/delete logic
- Must be unique per interface (can reuse across interfaces)
Example:
opnsense_firewall_vlan12_rules:
- sequence: "1" # First rule for VLAN12
action: pass
...
- sequence: "2" # Second rule for VLAN12
action: pass
...
- sequence: "99" # Block rule at end
action: block
...
Important:
- Sequence numbers don’t need to be consecutive
- Use gaps (e.g., 10, 20, 30) to allow inserting rules later
- Removing a rule from vars (but keeping its sequence on the firewall) will delete it
- Changing a sequence number creates a new rule and orphans the old one (which gets deleted)
Dependencies
This role has no dependencies on other Ansible roles, but requires:
- OPNsense firewall with API enabled
- API key with firewall management permissions
- Ansible Vault for storing API credentials
- Firewall aliases defined (if using aliases in rules)
- Firewall categories created (if using categories in rules) - see
opnsense_firewall_categoriesrole
Example Playbook
Basic Usage
---
- name: Configure OPNsense Firewall Rules
hosts: localhost
become: false
vars:
opnsense_firewall_rules:
vlan12:
- sequence: "1"
action: pass
interface: vlan12
description: "Allow VLAN12 HTTPS to Internet"
protocol: tcp
destination_port: "443"
roles:
- opnsense_firewall
Multiple Interfaces
---
- name: Configure Multi-Interface Firewall
hosts: localhost
become: false
vars:
opnsense_firewall_vlan10_rules:
- sequence: "1"
action: pass
interface: vlan10
description: "Allow management to all"
source_net: vlan10
opnsense_firewall_vlan12_rules:
- sequence: "1"
action: pass
interface: vlan12
description: "Allow servers to Internet"
protocol: tcp
destination_port: "443"
- sequence: "2"
action: block
interface: vlan12
description: "Block servers to management"
destination_net: vlan10
log: "1"
opnsense_firewall_rules:
vlan10: "{{ opnsense_firewall_vlan10_rules }}"
vlan12: "{{ opnsense_firewall_vlan12_rules }}"
roles:
- opnsense_firewall
Using Aliases
---
- name: Configure Firewall with Aliases
hosts: localhost
become: false
vars:
opnsense_firewall_vlan12_rules:
- sequence: "1"
action: pass
interface: vlan12
description: "Allow servers to web ports"
source_net: Internal_Servers # Alias
destination_port: Web_Ports # Alias
protocol: tcp
roles:
- opnsense_aliases # Create aliases first
- opnsense_firewall # Then create rules using them
What This Role Does
-
Iterates through interfaces:
- vlan10, vlan12, vlan14, vlan16, vlan18, vlan20, vlan22
- wg0, lan, wan
-
For each interface with defined rules:
- Fetches existing categories via
/api/firewall/category/searchItemand builds name → UUID mapping - Fetches existing rules via
/api/firewall/filter/search_rule - Builds sequence → UUID mapping for idempotency
- Creates new rules (sequence doesn’t exist) via
/api/firewall/filter/addRule - Updates existing rules (sequence exists but fields differ) via
/api/firewall/filter/setRule - Deletes orphaned rules (sequence exists on firewall but not in vars) via
/api/firewall/filter/delRule - Applies configuration via
/api/firewall/filter/apply
- Fetches existing categories via
-
Displays summary: Lists interfaces configured
OPNsense API Endpoints
Add Firewall Rule
POST /api/firewall/filter/addRule
Authorization: Basic (API key:secret)
Content-Type: application/json
Request body:
{
"rule": {
"enabled": "1",
"action": "pass",
"quick": "1",
"interface": "vlan12",
"direction": "in",
"ipprotocol": "inet",
"protocol": "tcp",
"source_net": "any",
"destination_net": "any",
"destination_port": "443",
"log": "1",
"description": "Allow HTTPS",
"statetype": "keep",
"categories": "category-uuid-here"
}
}
Response:
{
"result": "saved",
"uuid": "12345678-1234-1234-1234-123456789abc"
}
Update Firewall Rule
POST /api/firewall/filter/setRule/{uuid}
Authorization: Basic (API key:secret)
Content-Type: application/json
Request body: Same as addRule
Response:
{
"result": "saved"
}
Delete Firewall Rule
POST /api/firewall/filter/delRule/{uuid}
Authorization: Basic (API key:secret)
Response:
{
"result": "deleted"
}
Search Rules by Interface
GET /api/firewall/filter/search_rule?interface={interface_id}
Authorization: Basic (API key:secret)
Returns all rules for an interface with their UUIDs and sequence numbers.
Apply Configuration
POST /api/firewall/filter/apply
Activates pending firewall rule changes.
Firewall Rule Fields
action
What to do with matching traffic.
Values:
pass: Allow trafficblock: Silently drop trafficreject: Drop and send rejection (RST/ICMP)
Example:
action: pass # Allow traffic
interface
Which network interface this rule applies to.
Values: vlan10, vlan12, vlan14, vlan16, vlan18, vlan20, vlan22, wg0, lan, wan
Homelab network design:
- VLAN10: Management (192.168.x.x/24)
- VLAN12: Servers (192.168.x.x/24)
- VLAN14: Desktops (192.168.x.x/24)
- VLAN16: WiFi Trusted (192.168.x.x/24)
- VLAN18: WiFi Guest (192.168.x.x/24)
- VLAN20: WiFi CCTV (192.168.x.x/24)
- VLAN22: Ethernet Guest (192.168.x.x/24)
- wg0: WireGuard VPN
- lan: Internal LAN (172.16.x.x/24)
- wan: Internet-facing
quick
Stop processing rules after first match.
Default: "1" (enabled)
Values:
"1": First match wins (typical)"0": Continue processing (rare)
direction
Traffic direction relative to interface.
Default: in
Values:
in: Incoming to interfaceout: Outgoing from interface
protocol
Network protocol.
Common values: tcp, udp, icmp, icmp6
Omit for any protocol.
source_net / destination_net
Source or destination address.
Values:
any: Any addressvlan10: Interface subnet- Alias name:
Internal_Servers - CIDR:
192.168.x.x/24 - Single IP:
192.168.x.x
source_port / destination_port
TCP/UDP port numbers.
Values:
- Single port:
"443" - Port range:
"8000-8999" - Alias:
Web_Ports
Omit for any port.
log
Log matched packets.
Default: "1" (enabled)
Values:
"1": Log matches"0": Don’t log
Logs location: OPNsense → Firewall → Log Files
statetype
Connection state tracking.
Default: keep
Values:
keep: Stateful (track connections)sloppy: Relaxed state matchingsynproxy: Proxy TCP handshakenone: Stateless
categories
Assign a rule to a firewall category for organization and filtering.
Usage: Specify the category name (not UUID). The role automatically looks up the UUID.
Prerequisites: Categories must exist in OPNsense before assigning them to rules. Use the opnsense_firewall_categories role to create them first.
Example categories:
DNS: DNS and NTP related rulesWeb: HTTP/HTTPS access rulesManagement: SSH, RDP, SMB, SFTP rulesMonitoring: Grafana, Centreon, InfluxDB, SNMP rulesBlock: All block/deny rules
Example:
- sequence: "1"
action: pass
interface: vlan12
description: "Allow DNS requests"
protocol: udp
destination_port: "53"
categories: "DNS"
- sequence: "2"
action: pass
interface: vlan12
description: "Allow HTTPS to Grafana"
protocol: tcp
destination_port: "3000"
categories: "Monitoring"
Benefits:
- Filter rules by category in OPNsense UI
- Color-coded rule visualization
- Easier rule auditing and management
Rule Processing Order
OPNsense processes rules top to bottom:
- First matching rule with
quick: "1"wins - If no quick rules match, last matching rule wins
- Default rule: Block all (implicit at end)
Example:
# Rule 1 (quick): Allow HTTPS
- sequence: "1"
action: pass
quick: "1"
destination_port: "443"
# Rule 2: Block all
- sequence: "2"
action: block
quick: "1"
Result: HTTPS allowed (Rule 1), everything else blocked (Rule 2)
Network Segmentation Example
Typical homelab segmentation:
# Management VLAN: Allow all
opnsense_firewall_vlan10_rules:
- sequence: "1"
action: pass
interface: vlan10
description: "Allow management to everything"
# Servers VLAN: Internet only, block inter-VLAN
opnsense_firewall_vlan12_rules:
- sequence: "1"
action: pass
interface: vlan12
description: "Allow servers to Internet"
destination_net: "!vlan10,!vlan12,!vlan14"
- sequence: "2"
action: block
interface: vlan12
description: "Block servers inter-VLAN"
log: "1"
# Guest VLAN: Internet only, block everything else
opnsense_firewall_vlan18_rules:
- sequence: "1"
action: pass
interface: vlan18
description: "Allow guest to Internet only"
destination_net: "!vlan10,!vlan12,!vlan14,!vlan16"
- sequence: "2"
action: block
interface: vlan18
description: "Block guest to internal"
log: "1"
Using Aliases in Rules
Aliases simplify rules:
Without alias:
source_net: "192.168.x.x,192.168.x.x,192.168.x.x"
With alias:
source_net: Internal_Servers
Prerequisites: Create aliases first (see opnsense_aliases role).
Logging Considerations
Enable logging (log: "1") for:
- ✅ Block rules (see what’s denied)
- ✅ Security-critical rules
- ✅ Troubleshooting rules
Disable logging (log: "0") for:
- ❌ High-traffic rules (performance)
- ❌ Well-established rules
View logs: OPNsense → Firewall → Log Files → Live View
Idempotency
Role uses sequence-based idempotency:
- Each rule has a unique
sequencenumber per interface - Sequence numbers map to UUIDs on the firewall
- Create: New sequence → creates rule
- Update: Existing sequence with changed fields → updates rule
- Delete: Sequence on firewall but not in vars → deletes rule
- Only applies configuration if changes were made
- Safe to run repeatedly
Important: Removing a rule from your vars file will delete it from the firewall on the next run.
Security Considerations
- API Credentials: Stored in Ansible Vault
- HTTPS: Uses SSL/TLS for API calls
- Basic Auth: API key/secret authentication
- Certificate Validation: Enabled by default
- Least Privilege: Only open necessary ports
- Default Deny: Implicit block all at end
Tags
This role does not define any tags. Use playbook-level tags if needed:
- hosts: localhost
roles:
- opnsense_firewall
tags:
- opnsense
- firewall
- rules
- security
Notes
become: falserecommended (no root needed)- Rules processed per interface in main.yml loop
- Configuration applied per interface after any changes
- Sequence numbers must be unique per interface
- Removing a rule from vars will delete it from the firewall
- Rule order matters (first match wins with quick)
- Default implicit rule: Block all
Troubleshooting
”Authentication failed” errors
Cause: Invalid API key/secret
Solution: Verify credentials in vault and test API manually.
Rules not appearing in firewall
Cause: Apply configuration not called
Solution: Check role output for apply task execution.
Traffic blocked unexpectedly
Check rule order:
# Via OPNsense UI
# Firewall → Rules → Interface
# Review rule order
Check logs:
# Firewall → Log Files → Live View
# Look for blocked traffic
Rule conflicts
Cause: Multiple rules matching same traffic
Solution: Review rule order, ensure quick rules are correct.
Testing the Role
Verify Rules Created
Via OPNsense UI:
- Log into OPNsense
- Firewall → Rules → [Interface]
- Should see configured rules
Via API:
curl -u "key:secret" \
https://opnsense-ip/api/firewall/filter/searchRule | jq
Test Traffic
# Test allowed traffic
curl https://example.com
# Test blocked traffic (should timeout/fail)
curl http://blocked-host
# Check firewall logs
# Firewall → Log Files → Live View
Best Practices
- Document rules: Use descriptive descriptions
- Default deny: Block by default, allow explicitly
- Log blocks: Monitor denied traffic
- Use aliases: Simplify rule management
- Group related rules: Organize by purpose
- Test incrementally: Add rules gradually
- Review logs: Monitor firewall activity
- Least privilege: Only allow necessary traffic
- Network segmentation: Isolate VLANs
- Version control: Store rules in git
- Sequence management: Use consistent sequence numbers per interface
- Review before running: Removing rules from vars will delete them from the firewall
Related Roles
This role is often used with:
- opnsense_aliases: Create aliases before rules
- opnsense_firewall_categories: Create categories before assigning them to rules
- opnsense_source_nat: Configure outbound NAT
License
MIT
Author
Created for homelab infrastructure management.