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.logon the guest shows the hook finding the seed volume and applying it.config.xml'sauthorizedkeysshould 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 doand 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).