Skip to main content
RunBook Academy

OPNsenseXLIII · Ansible-Driven Firewall ConfigurationAnsible foundations

Ansible for firewalls — concepts, trade-offs, and blast-radius discipline

Intermediate⏱ ~16 minansibleansible-playbookgit

What you'll learn

  • Explain why Ansible is preferred over raw API scripts for firewall configuration at fleet scale
  • Describe the push-based execution model and how it interacts with OPNsense agentless transport
  • Recognise the role of idempotency, change reporting, and serial batches in firewall automation safety
  • Evaluate the trade-offs between Ansible and direct API scripts for the team and the firewall estate

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.

A single firewall can be driven by raw API scripts. A fleet of firewalls, or a single firewall that is touched repeatedly over months, benefits from a configuration-management tool that knows the state of the world, can apply changes idempotently, and can report on what it changed. Ansible is the most common such tool in production sysadmin work today, and OPNsense has a community collection that maps Ansible’s module model onto the OPNsense REST API.

This lesson covers why Ansible is preferred over raw scripts at fleet scale, the push-based execution model, idempotency and its operational meaning, the blast-radius discipline, and the trade-offs you weigh when picking one approach over the other.

Why Ansible for firewalls

Raw API scripts work for one firewall at a time. Three situations push teams toward Ansible:

  1. Multi-firewall estates. A team running five firewalls across two data centres needs a single source of truth for rules, aliases, and backplane config. Ansible inventories give that; raw scripts do not.
  2. Repeated change. A firewall that is touched monthly — new IPs, new ports, new rules — accumulates drift. Ansible’s idempotent modules re-apply the desired state on every run, so drift is corrected automatically.
  3. Audit and change reporting. A regulated environment that asks “what changed on firewall X in the last 90 days” can answer from Ansible’s git-tracked playbooks + the changed flag. A bash + curl script that mutates the firewall leaves no such record.

The same firewall could be managed by either approach. The question is which one matches the team’s reality: a single engineer with three firewalls probably writes Ansible. A 30-engineer platform team certainly does.

Push-based execution (and how OPNsense is agentless)

Ansible normally connects to each managed node over SSH and runs the module there. OPNsense does not work that way: the modules never touch the firewall’s shell, and nothing is installed on the firewall at all. They execute on the controller and reach the firewall over its HTTPS API, which is why every play against OPNsense sets connection: local and passes the firewall’s address as a module parameter.

That is a genuine advantage rather than a workaround. The firewall needs no SSH access, no Python for Ansible, and no package installed for management purposes — only an API key/secret pair with the privileges the play needs. The management surface an operator has to secure is the one that already existed.

The community collection modules — ansibleguy.opnsense.rule, ansibleguy.opnsense.alias, and the rest — wrap that API. Each reads the current state with a search, compares it against the desired state, and applies the difference with the relevant add or set call. The module reports changed, ok or failed depending on whether there was a difference to apply.

Read-only / Safeansible --check --diff
$ ansible-playbook -i inventories/firewalls/hosts.yml playbooks/firewall-rules.yml --check --diff --limit fw-dc1-01
PLAY [firewall rules on dc1-01] ***************************************************

TASK [Ensure outbound allow for CI runners] **************************************
--- before
+++ after
@@ -1,1 +1,1 @@
-+        { action: pass, source: ci_runners_alias, destination: any, descr: CHG-2026-1314 }
changed: [fw-dc1-01]

PLAY RECAP ******************************************************************
fw-dc1-01                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Illustrative output

Idempotency: the operational meaning

An idempotent module is one where running the playbook twice produces the same end state as running it once. From the firewall’s perspective, that means the second run makes no write at all: the module reads the current state, finds it already matches, and sends nothing. A module that instead re-sent the same setRule every time would still leave the firewall correct, but it would write a new configuration revision on every run and bury real changes in a history of no-ops.

The operational meaning has three parts:

  1. Drift correction. If someone hand-edits a rule through the GUI, the next Ansible run sees that the rule exists but does not match the playbook’s desired state, and reapplies the desired state. Without idempotency, this would require a separate “reconcile” workflow.
  2. Re-running is safe. A playbook that timed out, or was interrupted by an on-call alert, can be re-run from the top. The second run is mostly no-ops, only the unfinished tasks apply.
  3. Change reporting is honest. The changed flag is a precise signal: this run actually changed something on this host. A changed count of zero across a fleet means the firewall matches its desired state exactly.

Idempotency is not free. A module has to read state before writing. The cost: extra API calls (typically 1-3 per object per run). The benefit: the playbook is repeatable, predictable, and safe to re-run.

Serial batches and blast-radius discipline

A run against 30 firewalls is not safe to perform in parallel. One badly-formed module call could lock the operator out of every firewall simultaneously. The discipline is serial batches: --serial 1 or --serial 5 limits how many hosts the play runs against at the same time. A 30-firewall estate typically runs --serial 1 against a single maintenance window.

Three operational patterns:

  1. Maintenance windows. All fleet changes happen during a scheduled change window. Out-of-window changes require manual override and a separate audit path.
  2. Canary first. The play runs against one firewall (the canary) first. The operator verifies the change looks right, then extends to --limit all.
  3. Auto-promotion. --serial 1 with a pause and a verification between batches. A failed batch halts the next from running (Ansible’s max_fail_percentage).

The blast-radius discipline turns a fleet-wide automation into a governed change. The same playbook, run naively in parallel, is an outage waiting to happen.

The trade-offs versus direct API scripts

AspectDirect API scriptAnsible
Lines of codeLessMore (roles, tasks, vars, handlers)
Onboarding new team membersFaster for one scriptFaster at scale (standard tools)
Drift correctionManualAutomatic on next run
Multi-firewall inventoryBuild your ownBuilt-in
Change reportingBuild your ownchanged flag, callback plugins
IdempotencyBuild your ownModule-provided
Pre-flight checks (per-firewall)ManualBuilt into the playbook
Required operator skillBash, curl, jqAnsible playbook syntax, modules

For a single firewall and a one-off change, a script is faster to write and easier to understand. For a fleet or a regular-cadence change, Ansible earns its overhead. The wrong choice is to write a fleet of curl-based scripts that duplicate each other, each with its own drift, its own identifier for the same object, and its own audit story.

When not to use Ansible

Three situations where Ansible is the wrong tool:

  1. Truly one-off changes. A change that will not be repeated (a one-time migration) does not need a playbook. A script with the change ticket ID in the comments is the right tool.
  2. Discovery. Reading the firewall’s state to answer “what is the current rule for port 22?” is a curl call, not a playbook.
  3. Bootstrapping the Ansible controller itself. Before there is an Ansible controller, there is no Ansible. The first run, on the first firewall, may be a curl call from a debug session — followed by an Ansible role that captures the same configuration for reuse.

Summary

  • Ansible is the right tool when the change is one of many, the firewall is one of many, or the audit story needs to be defended.
  • OPNsense is agentless; Ansible uses push mode over the API surface. Community modules wrap the OPNsense REST endpoints.
  • Idempotency means re-running is safe and drift is corrected automatically. The changed flag tells the operator what actually happened.
  • Serial batches and blast-radius discipline turn a fleet run into a governed change. The disciplined run is --limit fw-canary --check --diff first, then --limit all in a serial cadence.
  • An Ansible playbook is not magic; the discipline of pre-flight, anti-lockout invariants, and post-flight verification lives in the playbook’s structure.

Knowledge check · 4 questions

  1. Q1. You have 12 firewalls that share rules and aliases that change weekly. Why is Ansible the right tool?

  2. Q2. An idempotent module running twice against a firewall produces the same end state as running it once; the second run reports `changed` only on objects that drifted.

  3. Q3. Which of the following are characteristics of Ansible that make it suitable for fleet firewall management? Select all that apply.

  4. Q4. You are running an Ansible playbook against 30 firewalls. The first firewall fails with a timeout mid-run. What is the most appropriate immediate action?

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