OPNsense as Code with Ansible: Deploy and Version Your Entire Infrastructure in One Command

Deploy and version your entire OPNsense infrastructure with Ansible: complete VLAN/firewall/WireGuard/CrowdSec playbook, GitOps and disaster recovery in < 15 minutes.

OPNsense as Code with Ansible: Deploy and Version Your Entire Infrastructure in One Command
Infrastructure as Code vs manual OPNsense configuration
IaC vs manual config: reproducibility, Git versioning, idempotency

1. Why Infrastructure as Code for a Homelab/SME?

After 11 episodes building a complete network security stack, one question stands out: how do you reproduce this work exactly on a new firewall after hardware failure, migration, or expansion? The answer: Infrastructure as Code (IaC).

Without IaC, every OPNsense configuration change is a manual action — clicking through the interface, copy-pasting rules, hoping nothing gets missed. With IaC, the entire configuration is code versioned in Git, replayable with a single command. For a homelab or SME, the benefits are concrete:

  • Reproducibility: new firewall fully operational in < 10 minutes
  • Versioning: git log shows every change — who, when, why
  • Idempotency: re-running the playbook = same state, never a regression
  • Living documentation: the code IS the documentation of your network
  • Disaster recovery: git clone + ansible-playbook = infrastructure restored
  • Collaboration: share the config without giving firewall access

Ansible is the ideal tool for OPNsense: agentless (no daemon to install), uses SSH and the OPNsense REST API, and has a specialized collection — ansibleguy.opnsense.

2. Prerequisites: Ansible, SSH and OPNsense API

Before writing the first line of YAML, configure three things: Ansible on the control machine, SSH access to OPNsense, and API credentials.

2.1 Install Ansible

# Ubuntu / Debian
sudo apt update && sudo apt install -y ansible python3-pip
pip3 install requests

# macOS
brew install ansible

# Verify
ansible --version
# ansible [core 2.16+]

2.2 Enable SSH on OPNsense

# OPNsense: System > Settings > Administration
# ✓ Enable Secure Shell
# ✓ SSH port: 22 (or custom port)
# ✓ Permit password login: disable after adding key

# Add your public key:
# System > Access > Users > admin > Authorized keys

# Test from control machine:
ssh admin@192.168.1.1 'echo OK'

2.3 Create OPNsense API User

# OPNsense: System > Access > Users
# Create user 'ansible-api' with admin rights
# Generate key/secret pair

# Test the API:
curl -k -u "${OPNSENSE_API_KEY}:${OPNSENSE_API_SECRET}" \
  "https://192.168.1.1/api/core/firmware/status"
# {"status":"none"} → OK
Ansible collection structure for OPNsense
ansibleguy.opnsense collection: file structure and available modules

3. ansibleguy.opnsense Collection: Install and Structure

The ansibleguy.opnsense collection is the reference for automating OPNsense via Ansible. It covers interfaces, VLANs, firewall rules, WireGuard VPN, DNS, CrowdSec, and much more.

# Install the collection
ansible-galaxy collection install ansibleguy.opnsense

# Recommended project structure
opnsense-ansible/
├── inventory/
│   ├── hosts.yml          # OPNsense hosts
│   └── group_vars/
│       └── opnsense.yml   # Global vars + API credentials
├── playbooks/
│   ├── site.yml           # Main playbook (full stack)
│   ├── vlans.yml          # VLANs only
│   ├── firewall.yml       # Firewall rules
│   ├── wireguard.yml      # WireGuard VPN
│   └── crowdsec.yml       # CrowdSec bouncer
├── roles/
│   └── opnsense_base/     # Base role (NTP, DNS, SMTP)
├── ansible.cfg
└── README.md
# inventory/hosts.yml
all:
  children:
    opnsense:
      hosts:
        fw-primary:
          ansible_host: 192.168.1.1
          ansible_user: admin
          ansible_ssh_private_key_file: ~/.ssh/id_ed25519
        fw-secondary:           # CARP HA (Episode 10)
          ansible_host: 192.168.1.2

# inventory/group_vars/opnsense.yml
opnsense_api_host: "https://192.168.1.1"
opnsense_api_key: "{{ lookup('env', 'OPNSENSE_API_KEY') }}"
opnsense_api_secret: "{{ lookup('env', 'OPNSENSE_API_SECRET') }}"
opnsense_ssl_verify: false

4. Complete Playbook: VLANs, Firewall, WireGuard, CrowdSec

Here is the main playbook that configures the entire stack in a single command.

4.1 VLANs and Interfaces

# playbooks/site.yml
---
- name: "BOTUM Stack — Full OPNsense Configuration"
  hosts: opnsense
  gather_facts: false
  collections:
    - ansibleguy.opnsense

  tasks:
    - name: "VLAN 10 — Management"
      ansibleguy.opnsense.vlan:
        vlan_id: 10
        interface: em0
        description: "Management VLAN"
        state: present

    - name: "VLAN 30 — IoT"
      ansibleguy.opnsense.vlan:
        vlan_id: 30
        interface: em0
        description: "Isolated IoT VLAN"
        state: present

4.2 Firewall Rules (Zero-Trust)

    - name: "Rule — Block IoT to LAN"
      ansibleguy.opnsense.rule:
        interface: "vlan30"
        source: "vlan30 net"
        destination: "vlan10 net"
        action: "block"
        description: "IoT cannot reach LAN/MGMT"
        log: true
        state: present

    - name: "Rule — DNS IoT to AdGuard only"
      ansibleguy.opnsense.rule:
        interface: "vlan30"
        source: "vlan30 net"
        destination: "192.168.20.5"
        destination_port: "53"
        protocol: "tcp/udp"
        action: "pass"
        state: present

4.3 WireGuard VPN and CrowdSec

    - name: "WireGuard — Main instance"
      ansibleguy.opnsense.wireguard_server:
        name: "botum-vpn"
        port: 51820
        private_key: "{{ lookup('env', 'WG_PRIVATE_KEY') }}"
        tunnel_address: "10.10.0.1/24"
        state: present

    - name: "CrowdSec — enable package"
      ansibleguy.opnsense.package:
        name: "os-crowdsec"
        state: present

    - name: "CrowdSec — configure bouncer"
      ansibleguy.opnsense.crowdsec:
        enabled: true
        bouncer_api_key: "{{ lookup('env', 'CROWDSEC_BOUNCER_KEY') }}"
        crowdsec_api_url: "http://192.168.20.10:8080"
        state: present

# Run: ansible-playbook -i inventory/hosts.yml playbooks/site.yml
# Dry-run: ansible-playbook ... --check
GitOps workflow for OPNsense with Ansible
GitOps for OPNsense: commit → CI/CD → automated deploy → rollback ready

5. Versioning Config in Git (GitOps for OPNsense)

The power of IaC comes from Git versioning: every modification is tracked, reversible, and automatically documented.

# Initialize the Git repository
git init && git add . && git commit -m "feat: complete BOTUM stack (12 episodes)"

# .gitignore — never commit secrets
*.vault
.env
secrets/

# Encrypt secrets with ansible-vault
ansible-vault encrypt inventory/group_vars/vault.yml

# Daily GitOps workflow:
# 1. Modify a playbook
# 2. git commit -m "fix: stricter IoT rule"
# 3. git push origin main
# 4. CI/CD applies automatically

6. Idempotent Strategy: Re-run Without Breaking Anything

Idempotency is Ansible's fundamental property: running the same playbook 10 times always produces the same final state, without creating duplicates or breaking what works.

# Test idempotency before deploying
ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check
# → "changed=0 unreachable=0 failed=0" = already at desired state

# Diff mode: see exactly what would change
ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check --diff

# Targeted runs with tags
ansible-playbook site.yml --tags "vlans"
ansible-playbook site.yml --tags "firewall"
ansible-playbook site.yml --skip-tags "crowdsec"
CI/CD pipeline GitHub Actions for OPNsense Ansible
CI/CD pipeline: lint → dry-run → deploy → Telegram notification

7. CI/CD Integration: GitHub Actions and Gitea

Automate deployment on every commit. When you push a change to the main branch, the pipeline validates and automatically deploys.

# .github/workflows/deploy-opnsense.yml
name: Deploy OPNsense Stack

on:
  push:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install ansible-lint
      - run: ansible-lint playbooks/site.yml

  deploy:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Ansible
        run: |
          pip install ansible
          ansible-galaxy collection install ansibleguy.opnsense
      - name: Deploy
        env:
          OPNSENSE_API_KEY: ${{ secrets.OPNSENSE_API_KEY }}
          OPNSENSE_API_SECRET: ${{ secrets.OPNSENSE_API_SECRET }}
        run: ansible-playbook -i inventory/hosts.yml playbooks/site.yml

8. Disaster Recovery: Restore in One Command

The most feared scenario: the firewall dies. With the IaC stack, disaster recovery becomes a routine exercise.

# SCENARIO: Primary OPNsense offline
# Target restore time: < 15 minutes

# Step 1: Provision new OPNsense (~5 min)
# Step 2: Clone Ansible config
git clone https://gitea.botum.ca/botum/opnsense-ansible.git

# Step 3: Deploy full stack (~8 min)
OPNSENSE_API_KEY=xxx OPNSENSE_API_SECRET=yyy \
  ansible-playbook -i inventory/hosts.yml playbooks/site.yml

# ✓ VLANs, Firewall, WireGuard, CrowdSec, Suricata restored

# Step 4: Verify
ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check
# changed=0 → Stack 100% compliant
Complete BOTUM OPNsense stack 12 episodes summary
BOTUM Stack complete: 12 security layers, from Proxmox to Ansible IaC

9. Conclusion: Full 12-Episode Stack Recap

With Episode 12, the BOTUM OPNsense Stack is complete. Across 12 episodes, we built an enterprise-grade network security infrastructure — reproducible, versioned, and fully automated:

  • Episode 1 — OPNsense on Proxmox: hypervisor foundation
  • Episode 2 — VLANs Zero-Trust: network segmentation
  • Episode 3 — CrowdSec Collaborative IPS: community blocking
  • Episode 4 — WireGuard VPN: secure remote access
  • Episode 5 — fail2ban Hardening: brute-force protection
  • Episode 6 — NAC: network access control
  • Episode 7 — Suricata IDS/IPS: deep packet inspection
  • Episode 8 — AdGuard Home + DoH/DoT: filtered encrypted DNS
  • Episode 9 — Grafana + InfluxDB: network monitoring
  • Episode 10 — CARP High Availability: infrastructure resilience
  • Episode 11 — Wazuh SIEM: log correlation and detection
  • Episode 12 — Ansible as Code: automation and GitOps

The stack is now fully reproducible: a new team member can deploy the entire infrastructure in under 30 minutes, from scratch, using Ansible.

Coming next (Articles 13-15): Automated OPNsense config backup, Let's Encrypt certificates with ACME, and traffic analysis with Netflow + ntopng — to complete the stack.

📥 Guide PDF complet

Téléchargez ce guide en PDF pour le consulter hors ligne.

⬇ Télécharger le guide (PDF)

🚀 Aller plus loin avec BOTUM

Ce guide couvre les bases. En production, chaque environnement a ses spécificités. Les équipes BOTUM accompagnent les organisations dans le déploiement, la configuration avancée et la sécurisation de leur infrastructure. Si vous avez un projet, parlons-en.

Discuter de votre projet →
OPNsense Series 📋 Complete series →