1 Usage
Louis Raymond edited this page 2026-08-19 13:48:46 -04:00

Deploying an OPNsense VM

This assumes a template built from this repo already exists on your Proxmox node (see Development for building and importing one).

Standard case: Proxmox's native Cloud-Init fields

The common case — WAN + LAN (DHCP or static), an SSH key, a hostname — needs nothing beyond what Proxmox's Cloud-Init integration already supports directly. No custom snippet, no extra tooling.

Via the GUI: clone the template, open the VM's Cloud-Init tab, set the IP configuration per NIC, add your SSH key, then start the VM.

Via qm:

qm clone 9000 101 --name fw-edge01 --full
qm set 101 --ipconfig0 ip=203.0.113.10/24,gw=203.0.113.1   # WAN
qm set 101 --ipconfig1 ip=192.168.10.1/24                   # LAN
qm set 101 --sshkeys ~/.ssh/id_ed25519.pub
qm set 101 --nameserver 9.9.9.9
qm start 101

The first NIC becomes WAN, the second LAN — matching the base image's own default assignment, so nothing needs overriding.

Via OpenTofu (terraform/modules/opnsense-vm):

module "edge_firewall" {
  source = "path/to/terraform/modules/opnsense-vm"

  target_node    = "pve1"
  vm_name        = "fw-edge01"
  template_vm_id = 9000

  network_interfaces = [
    { bridge = "vmbr0", ip_config = { ipv4 = "dhcp" } },              # WAN
    { bridge = "vmbr1", ip_config = { ipv4 = "192.168.10.1/24" } },   # LAN
  ]

  ssh_keys    = [file("~/.ssh/id_ed25519.pub")]
  dns_servers = ["9.9.9.9"]
}

See terraform/examples/edge-firewall/ for a runnable version.

Advanced case: VLANs, non-default roles, seeded API keys

When you need something standard Cloud-Init fields can't express, author a user-data snippet carrying the opnsense: extension and point the VM at it instead of (or on top of) the native fields — see that doc for the full schema and terraform/examples/branch-with-vlans/ for a worked OpenTofu example.

Verifying a deployment

  • First boot, seed present: /var/log/opnseed.log on the guest shows the hook finding the seed volume and applying it. config.xml's authorizedkeys should decode to your real key, not the shipped placeholder.
  • No seed attached: the VM still boots cleanly to a login prompt — no console assignment menu, no GUI setup wizard — using the template's baked in defaults (DHCP everywhere, SSH by key only, no default key set).
  • Reboot with the same seed: the hook logs instance-id ... already applied, nothing to do and makes no changes.

If a generated API key was requested, retrieve it once from /root/.opnseed-api-keys.txt on the guest (root-only, plaintext, not regenerated on reboot).