Skip to main content
RunBook Academy

← All labs in OPNsense

Lab · advanced · ~90 min

Lab: Configure OPNsense backup and restore to a disposable VM

B · Nested virtualisationC · Simulation

Objectives

  • Configure the OPNsense automatic config backup plugin to ship encrypted backups to a remote destination
  • Take an on-demand backup before a controlled change
  • Restore the backup onto a fresh OPNsense VM and confirm the restored config matches the source
  • Verify the restored firewall is functionally identical: interfaces, rules, services, NAT, VPN
  • Document the restore procedure so the next operator does not have to rediscover it

Prerequisites

This lab builds an OPNsense backup-and-restore capability that actually works: scheduled automatic backups to encrypted remote storage, an on-demand backup taken before every controlled change, and a verified restore to a fresh OPNsense VM. By the end you have proven that the firewall’s configuration is recoverable from backup alone, without relying on tribal knowledge of “what the rules look like”.

The lab assumes a single-tenant OPNsense installation. The disposable VM is the only place where destructive operations are acceptable; the source firewall is never touched by anything more aggressive than reading its config.

Objective

By the end of this lab, you can:

  • Configure automatic encrypted backups of OPNsense to a remote destination, with rotation.
  • Take an on-demand backup before a controlled change and verify the SHA-256 fingerprint.
  • Restore a backup to a fresh OPNsense VM, preserving interfaces, firewall rules, NAT, DHCP, DNS, and VPN settings.
  • Diff the restored config.xml against the source to confirm byte-for-byte equivalence.
  • Document the restore procedure for the next operator.

Requirements

You need:

  • An OPNsense instance running in production mode with a known set of rules and services. The DHCP-scope lab’s state is a good baseline.
  • A remote destination for backups: an SFTP server, a Nextcloud WebDAV endpoint, or a local directory mounted over SSHFS. Encrypted-on-disk storage is mandatory; never store backups unencrypted.
  • A fresh OPNsense VM that has booted through the wizard but has not been configured. The restore target can be a different hypervisor; the lab uses Proxmox but any platform with a OPNsense ISO is fine.
  • Shell access on both the source firewall and the restore target.
  • A small amount of free disk on both — a config.xml is ~200 KB but backups with the plugin’s history can be ~50 MB each.

Tasks

Task 1: Install the backup plugin

System → Firmware → Plugins → os-backup → Install.

The plugin adds System → Configuration → Backups and the automatic-backup scheduler. Confirm:

# On OPNsense
ls /usr/local/sbin/configctl 2>/dev/null && echo "configctl present"
ls /usr/local/etc/inc/backup/ 2>/dev/null | head -5

The plugin writes helpers under /usr/local/etc/inc/backup/. If the directory is missing, the plugin install did not finish.

Task 2: Take an on-demand backup

System → Configuration → Backups → Download configuration.

A file config-<hostname>-<timestamp>.xml is offered. Save it locally and fingerprint it:

# On a workstation, after the download
sha256sum config-firewall-20260814T120000.xml > \
    config-firewall-20260814T120000.xml.sha256
cat config-firewall-20260814T120000.xml.sha256

The fingerprint is what you will compare against after the restore. If the two fingerprints differ, the restore changed the config — which is usually fine (interfaces renumbered) but must be diffed before declaring success.

Task 3: Configure the automatic backup destination

System → Settings → Cron:

Add a job to run daily at 02:00:

FieldValue
Enabledon
Minutes0
Hours2
Day of month*
Month*
Day of week*
Userroot
Command/usr/local/sbin/configctl -t -c backup.create

This is the simplest form. For an encrypted remote destination, the plugin’s GUI under System → Configuration → Backups → Settings is the easier path.

System → Configuration → Backups → Settings:

FieldValue
Enable automatic backupon
Backup destination/backup/opnsense
Encrypton
Encryption password(32+ random bytes from openssl rand)
Backup count to keep30

If the destination is on the OPNsense filesystem, configure a cron job to rsync or scp the file to the remote host. If the destination is SFTP, configure the OPNsense SSH client to authenticate with a key:

# On OPNsense
mkdir -p /root/.ssh
chmod 700 /root/.ssh
ssh-keygen -t ed25519 -N '' -f /root/.ssh/backup_key
cat /root/.ssh/backup_key.pub

Paste the public key into ~/.ssh/authorized_keys on the remote SFTP server.

Task 4: Verify the first automatic backup landed

Wait for the cron job to fire (or run it manually):

# On OPNsense
/usr/local/sbin/configctl backup.create
ls -l /backup/opnsense/

The output should show a backup-<timestamp>.xml file, encrypted with the passphrase you set in Task 3. Confirm the file is encrypted:

file /backup/opnsense/backup-*.xml
head -c 64 /backup/opnsense/backup-*.xml | xxd | head -2

If the file is plaintext XML, encryption is off — go back to Task 3.

Task 5: Set up the restore target VM

On the restore target VM, install OPNsense from the same ISO the source firewall runs. Boot through the initial wizard just enough to:

  • Set a hostname.
  • Configure the WAN interface to match the source’s WAN IP (or pick a unique IP if the source is still online and using its real IP).
  • Set a strong admin password.

Do not configure any firewall rules, NAT, or services. The whole point of the lab is to prove the backup carries that state without manual re-entry.

Confirm the restore target is reachable:

# Substitute your own value before running:
# RESTORE_TARGET is the disposable VM's WAN address from Task 5.
RESTORE_TARGET=203.0.113.2

# From your workstation
ssh "root@$RESTORE_TARGET" uname -a

Task 6: Upload the backup to the restore target

# RESTORE_TARGET: the disposable VM's WAN address (Task 5).
RESTORE_TARGET=203.0.113.2

scp config-firewall-20260814T120000.xml \
    "root@$RESTORE_TARGET:/tmp/config-restore.xml"

Then on the restore target:

# Substitute your own value before running:
RESTORE_TARGET=203.0.113.2

ssh "root@$RESTORE_TARGET"

# On the restore target: move the file where OPNsense expects it
cp /tmp/config-restore.xml /conf/backup/config-restore.xml

# Verify the size and timestamp
ls -la /conf/backup/

Task 7: Restore through the GUI

System → Configuration → Backups → Restore:

FieldValue
Configuration file/conf/backup/config-restore.xml
Encryption password(paste the passphrase)
Restore areaAll (interfaces, services, rules)

Click Restore configuration. The GUI reboots OPNsense.

If the restore target has the same WAN IP as the source firewall, the network will flap. If they share the same IP and both are online, the source firewall keeps its IP and the restore target will fail to bind. The lab uses a unique restore-target WAN IP to keep both online during verification.

Task 8: Diff the restored config.xml

Once the restore target is back up, compare the configurations:

# Substitute your own values before running:
SOURCE_WAN=203.0.113.1
RESTORE_TARGET=203.0.113.2

# From a workstation with both firewalls reachable

# Source
ssh "root@$SOURCE_WAN" cat /conf/config.xml > source-config.xml

# Restored
ssh "root@$RESTORE_TARGET" cat /conf/config.xml > restored-config.xml

# Diff
diff -u source-config.xml restored-config.xml | head -60

The diff will show some expected differences:

  • The <theme>, <timezone>, <dnsserver> may differ if the two installs were set up differently.
  • The <revision> tag carries a timestamp and revision number.
  • The <installedpackages> block may list different versions if the restore target has not run the firmware upgrade yet.
  • Interface MAC addresses will differ if the hardware is different. This is normal.

A surprising diff is a problem. Look for:

  • Missing <rule> entries — the rules did not restore.
  • Missing <ipsec> or <wireguard> blocks — the VPN did not restore.
  • Missing <unbound> block — DNS did not restore.

If any of those is missing, the restore did not carry that section. Re-run the restore from the GUI and check the log under System → Log → General for restore errors.

Task 9: Verify the restored firewall is functionally identical

A diff is the static check. The dynamic check is whether the firewall behaves the same way:

# On the restored firewall
pfctl -sr | wc -l            # should match the source's rule count
pfctl -sn | wc -l            # NAT rules should match
ipsec statusall              # IPsec SA list should be empty (no peers yet)
wg show                      # WireGuard interfaces should be present
unbound-checkconf            # Unbound config should validate

On a workstation behind the restored firewall:

# Each of these should work the same as on the source
nslookup services.lab.local  # host override
ping 10.10.10.50             # LAN reachability
curl http://10.10.50.20      # any service the LAN provides

If any of these fail, the diff in Task 8 will tell you why. The common pattern is “the rule is present, but the interface assignment is wrong” — the rule references wan but the restored firewall named the WAN differently.

Task 10: Document the restore procedure

The lab’s value is not the restored VM; it is the written procedure the next operator follows. Write a one-page runbook:

OPNsense Restore Procedure
==========================

When: firewall is lost, config.xml is corrupted, or a new VM
  must replace the current firewall.

Pre-flight:
  1. Identify the most recent good backup:
     ssh backup-server ls -t /backups/opnsense/ | head
  2. Verify the backup's SHA-256 fingerprint against the
     manifest in the password manager.
  3. Provision a fresh OPNsense VM with the same OPNsense
     version as the source.

Restore:
  1. Boot the new VM through the wizard; assign a unique
     WAN IP (not the source's).
  2. scp the backup file to /conf/backup/config-restore.xml.
  3. System → Configuration → Backups → Restore →
     select the file → enter the encryption password →
     Restore.
  4. Wait for the reboot. Log in.

Verification:
  1. diff source-config.xml restored-config.xml; only
     expected differences (revision, MAC, packages).
  2. pfctl -sr | wc -l matches the source's count.
  3. Each NAT, VPN, and DNS rule is present and active.
  4. A ping from a LAN host to a known target succeeds.

Cutover:
  1. Once verification passes, schedule a maintenance window.
  2. Power off the source firewall.
  3. Re-IP the restore target to the source's WAN address.
  4. Verify production traffic resumes.

The runbook lives in the team’s docs/runbooks/ directory, not in this lab file. The lab’s job is to teach the procedure; the runbook’s job is to be there when the procedure is needed.

Validation

  • An automatic backup is scheduled, with rotation, to an encrypted destination.
  • The on-demand backup’s SHA-256 fingerprint is recorded.
  • The restore target VM boots, restores, and reboots cleanly.
  • diff source-config.xml restored-config.xml shows only the expected differences (revision, MAC, package versions).
  • pfctl -sr | wc -l, pfctl -sn | wc -l on the restore target match the source firewall.
  • A LAN-side ping succeeds through the restored firewall.
  • A written restore runbook exists in the team’s documentation.

Expected Result

A working automatic backup pipeline that ships encrypted backups to remote storage, plus a verified procedure to restore those backups to a fresh OPNsense VM. The restored VM is functionally identical to the source — same rules, same NAT, same VPN, same DNS overrides. The operator has a runbook that documents the restore end to end.

Troubleshooting

The cron job never fires. OPNsense’s cron is crontab -e-compatible. Verify:

configctl cron jobs list
crontab -l | grep backup

If the job is in crontab -l but not firing, check System → Settings → Cron → Enable cron and the firewall rules on WAN that block outbound SSH/SFTP.

The encrypted backup is rejected on restore. Wrong passphrase. The passphrase is set at backup time and is not recoverable from OPNsense — it lives in the team’s password manager, not on the firewall. If the passphrase is lost, the backup is unrecoverable.

The restored firewall boots but services are missing. The diff shows <installedpackages> blocks differ. The plugins are installed but not configured. Re-run System → Firmware → Plugins → + Install for each missing plugin, then re-apply the configuration through the plugin’s GUI tab.

The diff shows rules are missing on the restore target. The restore GUI was set to “Partial” instead of “All”. Re-run the restore with the correct scope.

The restored firewall cannot reach the LAN. The restore target’s interface assignment does not match the source’s physical layout. The backup carries interface roles (“LAN”, “WAN”), not interface names (em0, igb0). If the restore target’s em0 is plugged into the WAN network but the source’s em0 was LAN, the rule assignments are reversed. Always double-check the physical-to-logical mapping after a restore.

Cleanup

The disposable VM is disposable. Decommission it now.

# Substitute your own values before running:
VMID=910
SOURCE_WAN=203.0.113.1

# On the restore target
rm -f /conf/backup/config-restore.xml
poweroff

# On the hypervisor (Proxmox example)
qm shutdown "$VMID" --forceStop 1
qm destroy "$VMID"

# Confirm the source firewall is untouched
ssh "root@$SOURCE_WAN" uptime

# Remove the on-demand backup from the local workstation
shred -u config-firewall-20260814T120000.xml
shred -u config-firewall-20260814T120000.xml.sha256

The automatic backup schedule on the source firewall stays. The on-demand backup file from Task 2 is removed from local disk; it lives on the remote backup server, encrypted.

What you learned

  • Backups are useless if the restore has not been tested. The disposable VM is how you test the restore — and the test is what makes the backup count.
  • config.xml carries every secret on the firewall. Encrypted at rest, transported over an authenticated channel, and the passphrase lives in the password manager, not on the firewall.
  • A restore is not just “diff the configs”. It is “diff the configs, verify the rule count, verify each service, verify the LAN can reach the firewall”. The static diff catches missing rules; the dynamic checks catch broken interface assignments.
  • The runbook is the deliverable, not the lab. The lab teaches the procedure; the runbook is the procedure when the operator is half-asleep at 03:00 and the firewall is down.

Deliverables

  • · An automatic backup schedule that retains the last 30 daily backups
  • · An on-demand backup file with a SHA-256 fingerprint
  • · A restored OPNsense VM with the same interfaces, firewall rules, NAT, and VPN settings as the source
  • · A diff report between the original config.xml and the restored config.xml
  • · A written restore procedure in the team's runbook

Verification status

Last reviewed
2026-08-14
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.