Wheelhouse docs

Read the migration report#

report.md is the half of the import that is not commands. It lists, in the order the importer worked, everything that came across, everything that came across in a different shape, and everything that could not come across at all. The third list is the work you still have to do.

Read it before you boot anything.

Before you start#

  • report.md from a run of the importer with --out.
  • The old router still running, or at least still reachable, so you can check anything the report raises.

What the file looks like#

markdown
# OPNsense import report

Source: `/home/you/config.xml`

- translated: 31
- translated differently: 3
- not translated: 9

## Translated

- **interfaces**: lan igc0 -> eth0 (192.0.2.1/24)
- **nat**: forward 443 -> 192.0.2.10 (web)
...

## Translated differently

- **qos**: the shaper
  OPNsense shapes with dummynet pipes in both directions; this is CAKE on egress, which
  is the direction that actually controls a home uplink

## Not translated

- **plugins**: OpenVPN servers and clients
  configured and running upstream; set it up again on the new box

Each entry is an area, a detail, and — in the second and third sections — the reason. The counts at the top are the same three numbers the importer prints when it finishes.

Section 1 — Translated#

Everything that came across as it was. Read it for one purpose: to check that the count matches what you expect. The lines worth looking at every time:

Line beginsCheck
**interfaces**:One line per port, with the mapping and the address. This is where a swapped --map shows itself.
**nat**: forwardOne line per port forward. Count them against the old router.
**dhcp**: N reservationsThe number, against the old router's static mappings.
**routes**:One line per static route.
**dns**:The forwarder's listen address, then one line per static host.

Section 2 — Translated differently#

Things that arrived, but not in the shape they left. Nothing here is broken; each one is a decision the importer made because VyOS expresses the idea differently. Every entry carries its reason.

The ones you will actually see:

Interface names. lan was igc0, now eth0 appears for every port when you did not pass --map. If any of them is wrong, fix the mapping and re-run rather than editing the commands file — several later sections derive addresses from the interface section.

A forward matched on something other than the WAN. OPNsense can match a forward on an interface group or on a specific address; the import puts it on the WAN interface. If the old rule was deliberately scoped to a second uplink or a VLAN, re-add that scope from the NAT page.

The PXE boot file. The old router handed out a different loader per client architecture; VyOS has one file name per subnet, so one was chosen. If you boot both UEFI and legacy clients, dnsmasq is the answer.

The shaper. Two dummynet pipes became one CAKE policy on egress. See Shape a link with CAKE.

Dynamic DNS. Each enabled ddclient account is written to ddns.commands, beside config.commands, as a complete entry with a placeholder where its password or API token goes. Credentials do not travel through the importer's output. Put each secret in place of its placeholder, paste the file in configure mode and commit; see Keep a name pointed at a changing address. Skipping it breaks nothing on the day, and the names go stale the next time your provider changes your address.

The router's own IPv6 rules. They are not read. What is written is default-deny on both families, so a box that gains IPv6 later is closed rather than open. Add the v6 rules you need from the Firewall page.

Section 3 — Not translated#

The work list. Every entry names something the old router was doing that the new one will not do until you set it up. Here is each one the importer can produce, what it means, and where to go.

Plugins that were running#

- **plugins**: OpenVPN servers and clients
  configured and running upstream; set it up again on the new box

The importer checks eleven OPNsense plugin trees and names any that were enabled.

Reported asWhere it goes on Wheelhouse
OpenVPN servers and clientsAn OpenVPN server for remote devices. Protect → OpenVPN → Add instance makes a server, a client or one end of a site-to-site tunnel; its certificates can be pasted from the old router or made new, and a server's clients get new files from its settings.
IPsec tunnelsAn IPsec tunnel to a third-party device
WireGuard tunnelsWireGuard for a laptop, between two sites
TailscaleJoin a tailnet from the router — it runs as an app here
Suricata (the IDS plugin)Turn on Suricata IDS — VyOS packages it natively
the routing daemons (quagga)A BGP session, an OSPF area — FRR is built in
captive portalNot available. There is no captive portal in this product.
the caching proxyNot available.
ntopngAvailable as an app from the catalogue; see Install an app. Flow data can also go to a collector — Export flows.
the UniFi discovery helperNot available as a first-class feature; mDNS repeating covers some of what people used it for.
monit service checksNot available. Uptime Kuma is in the catalogue.

NAT reflection#

- **nat**: NAT reflection on 3 forwards
  the router lets LAN clients reach these services by the WAN address; on VyOS that needs
  a second destination rule matched on the LAN plus source NAT back to the router, which
  is a decision, not a translation. Split-horizon DNS is usually the better answer

Two ways to close this, and the second is better on most networks.

  1. Hairpin the forward. The NAT page's New port forward panel has a checkbox for it, and stages the extra destination rule plus the source NAT that makes the reply come back through the router. Step by step in Publish a service.
  2. Answer the name differently inside. Give the service a name in an authoritative zone on the router's own resolver that points at its inside address, so LAN clients never touch the public one. Authoritative records for local names.

UPnP#

- **upnp**: UPnP is the agent's, not a daemon's
  it is not in the command file at all: on this product every mapping is a commit to
  the router's own configuration, so UPnP is an agent setting

Since 0.8.0 the agent speaks UPnP IGD and NAT-PMP itself. The base still has no service upnp configuration path and no miniupnpd package — which is exactly why the agent does it, writing one destination NAT rule and one forward-filter accept per mapping, in the reserved band 4000-4199.

So the old router's settings do come across, but not into config.commands: the importer writes them to upnp.json beside it, with the WAN interface, the LAN interface and the client subnets taken from the old router's miniupnpd section, and this product's own defaults for everything miniupnpd has no equivalent of — the slot band, the longest lease and the commits-per-minute ceiling. Apply it after the install under Services → UPnP, or with PUT /api/upnp.

Two things in the old configuration are refused rather than guessed at, and the report names them: miniupnpd's allow/deny permission rules, which have no equivalent here — the bounds are the client subnets and the external port range, and a client may only ever map a port to its own address — and the uplink rates miniupnpd reports to clients, which belong to the shaper.

Wildcard DNS records#

- **dns**: *.lab.example.com and lab.example.com -> 192.0.2.10

A wildcard becomes an authoritative zone:

set service dns forwarding authoritative-domain lab.example.com records a any address 192.0.2.10
set service dns forwarding authoritative-domain lab.example.com records a @ address 192.0.2.10

any is the wildcard — the validator refuses a literal * — and @ is the apex, which the old router answered for too, because OPNsense writes that entry as dnsmasq's address=/lab.example.com/….

The report also says the zone is now authoritative, and that is the sentence to read twice: nothing under that domain is forwarded upstream any more. It is what the old router already did (local=/lab.example.com/), but it means every name in the domain this router should answer for has to be in the zone. The importer moves the other host entries in a wildcarded domain into it for that reason; a name you add later has to go there too, not into system static-host-mapping, or the wildcard will swallow it.

Names for DHCP clients#

The old router registered its leases in DNS so printer.lan resolved without anyone typing it anywhere. The VyOS forwarder does not. Either give the hosts that need names a reservation plus a static host mapping, or hand DNS to dnsmasq, which does register its own leases.

Per-reservation resolvers#

These come across. A reservation that pinned its own resolvers — or its own router — keeps them, because this platform takes both per mapping as well as per subnet:

set service dhcp-server shared-network-name LAN subnet 192.0.2.0/24 \
    static-mapping smart-bulb option name-server 9.9.9.9

A host pinned to a resolver upstream is usually pinned for a reason nobody remembers, and silently joining it to the subnet's resolver is the kind of change that is only noticed weeks later.

Local accounts#

- **system**: 4 local accounts
  password hashes do not carry between FreeBSD and Linux; the installer sets the console
  and web UI passwords

Create a Wheelhouse account per person, with the lowest role that works, on Admin → Users — Add an operator. The router's own shell accounts are separate and are covered in The console account and its SSH keys.

Virtual IPs#

Extra addresses the old router carried. Add them as additional addresses on the interface, from the interface panel:

set interfaces ethernet eth1 address 198.51.100.5/32

If they were a CARP pair rather than plain aliases, that is VRRP, not an address.

Interfaces that are not Ethernet ports#

Tunnel and VPN interfaces are configured by their own service. See the plugin table above.

Anything that was disabled#

Disabled interfaces, disabled forwards and disabled routes are skipped and reported. That is usually what you want; if one of them was disabled by accident, re-enable it on the old router and re-run the import rather than hand-editing the commands.

Rules on an interface that is not part of the import#

- **firewall**: rule on 'opt1' (guest to internet)
  that interface is not part of this import

Map that interface with --map and re-run, or re-create the rules by hand once the port exists on the new box.

Check it worked#

You have read the report when you can answer these without looking again:

  • How many port forwards came across, and does that match the old router?
  • Which interface is the LAN on the new box?
  • What is on the Not translated list that your network actually uses?

Turn the third answer into a list, and work through it after the new box is up. A migration is not finished when the box boots; it is finished when that list is empty.

See also#

Updated 2026-09-03 migration opnsense