CLI for migrating firewall configs between pfSense and OPNsense, with section-aware diff analysis and pre-restore validation.
WARNING: This tool can break network connectivity or lock you out if used incorrectly. It is intended for restore/migration onto fresh destination installs. Do not restore to production systems unless you fully understand the generated changes and have rollback access.
cargo build --releaseAdd target/release/pfopn-convert to your PATH, or run it directly from that path.
convert is baseline-merge by design. You need:
sourceexport: live config from the platform you are migrating from.target baselineexport: fresh/default config export from the destination platform/version.
Then:
- run
convertwith--target-file <target-baseline.xml>to generate a new output XML. - run
verifyandmigrate-checkon that output. - restore/import the generated output on the destination firewall.
pfSense -> OPNsense:
pfopn-convert convert /path/source-pfsense.xml \
--from pfsense \
--to opnsense \
--target-file /path/opnsense-fresh.xml \
--output /tmp/converted-opnsense.xml
pfopn-convert verify /tmp/converted-opnsense.xml --to opnsense --strictOPNsense -> pfSense:
pfopn-convert convert /path/source-opnsense.xml \
--from opnsense \
--to pfsense \
--target-file /path/pfsense-fresh.xml \
--output /tmp/converted-pfsense.xml
pfopn-convert verify /tmp/converted-pfsense.xml --to pfsense --strictThen restore the generated XML on destination. Optional flags for initial migration:
--lan-ip <IPv4>only if you want to change the LAN IP/subnet in the output.--disable-dhcponly if you want DHCP services off in the generated output.
- Before you run this, set up the destination firewall and give its network ports the right names (like
wanandlan). - If the hardware is different, the port device names will change (for example
igb0becomesvtnet0). - The tool matches by the logical names (
wan,lan,opt1), not by device names. The destination baseline decides the rename. - This is intentional so configs still work when hardware device names change during migration.
- If a required physical port isn’t assigned in the destination baseline, the convert step will fail.
- Take a full config backup of the destination firewall before any restore.
- If testing in VM/lab, take a VM snapshot before first import.
- If restore reports interface assignment warnings, reboot the destination and restore the same generated XML a second time.
- This appears to be an intermittent OPNsense interface/apply behavior.
This is intended for safe migration to a different system first (for example lab/VM hardware), so you do not risk your live source firewall during initial validation. Use a fresh/default destination install for the baseline export whenever possible. Existing destination config state (especially bridges, prior interface assignments, gateways, or plugin-managed objects) can conflict with imported assignments and break post-restore behavior.
Example (pfSense -> OPNsense):
pfopn-convert convert pfsense-live.xml \
--to opnsense \
--target-file opnsense-fresh.xml \
--output converted-opnsense.xmlCompare two XML files and emit differences plus action analysis.
pfopn-convert diff <FILE1> <FILE2> [OPTIONS]Options:
--section <name>: focus on one logical section (system,interfaces,firewall,services,vpn,packages)--ignore <path-or-tag>: ignore path/tag (repeatable)--format <text|json>: output format--summary: print only counts--section-summary: print per-section counts sorted by conflict density--plan <file>: write action plan JSON--output <file>: write merged XML using safe insert-only actions--merge-to <left|right>: destination side for merge output (defaultright)- default
--outputbehavior also transfers OpenVPN dependencies required for migration:- referenced system users
- referenced certs
- referenced CAs
- and transfers missing WireGuard config sections (pfSense
<wireguard>/ OPNsense<OPNsense><wireguard>)
- opt-out flags:
--no-transfer-users--no-transfer-certs--no-transfer-cas
--strict: fail if any manual conflicts remain-v, --verbose: include identical entries-q, --quiet: minimal output
Show parsed structure and optional config detection.
pfopn-convert inspect <FILE> [--detect] [--plugins] [--section <name>] [--depth <N>]--plugins: show common plugin state detection (declared,configured,enabled) for migration planning.- includes
wireguard,openvpn,ipsec,kea-dhcp,isc-dhcp, andtailscale.
- includes
Convert one file toward a target platform.
pfopn-convert convert <INPUT> --output <FILE> --from <auto|pfsense|opnsense> --to <pfsense|opnsense> --target-file <FILE> [--backend <auto|kea|isc>]--from autodetects source from root tag;--tomust be explicit.--target-fileis required: provide a fresh/default config export from the destination platform.--outputis required: path for the generated XML.--backend auto|kea|isccontrols DHCP backend policy;autodefaults to Kea for OPNsense 26.x targets.--lan-ip <IPv4>is optional; use it only if you want to change LAN IP/subnet and remap LAN DHCP ranges.--disable-dhcpis optional; use it only if you want DHCP services off in the generated output.- The tool lines up interfaces by names like
wanandlan. The baseline tells it which device name (igb0,vtnet0) to use. - preflight fails if required physical-interface-backed logical assignments are missing from the target baseline; virtual-backed interfaces (VLAN, WireGuard, OpenVPN) can be created from source.
List top-level sections in both files, plus mapping hints.
pfopn-convert sections <FILE1> <FILE2> [--format <text|json>] [--extras] [--extras-json] [--verbose] [--mappings-file <path> | --mappings-dir <dir>]--extras: enable heuristic hints for likely moved/renamed sections by scanning nested paths.- also emits OpenVPN migration checks:
vpn_disabled_config_presentwhen disabled OpenVPN instances existvpn_dependency_gapwhen referenced users/certs/CAs are missing on target- also emits WireGuard migration checks:
wireguard_dependency_gapwhen WireGuard config exists on one side onlywireguard_disabled_config_presentwhen config exists but no enabled entries are detected- and plugin compatibility checks:
plugin_support_gapwhen plugin presence differs between sides- and IPsec dependency checks:
ipsec_dependency_gapwhen referenced certs/CAs/interfaces are missing on the opposite side
--extras-json: emit grouped extras plus unmatched section lists as JSON.--mappings-file <path>: load known section mappings from TOML file.--mappings-dir <dir>: load mappings from<dir>/sections.toml.- defaults are embedded;
--mappings-fileand--mappings-dirare mutually exclusive. --verbose: show mapping source (Using mappings: ...in text mode).
Quick migration-readiness scan for a single config.
pfopn-convert scan <FILE> [--to <pfsense|opnsense>] [--target-version <VERSION>] [--format <text|json>] [--verbose] [--mappings-dir <dir>]- reports detected platform/version/backend
- lists supported vs review-required top-level sections
- shows known plugins present and unsupported plugin packages (from plugin matrix + unknown package detection)
- with
--to, includes target compatibility hints for detected plugins - with
--target-version, includes target schema version metadata in scan output (informational only) --mappings-dir <dir>: load plugin matrix from<dir>/plugins.toml.--verbose: show mapping source (Using mappings: ...in text mode).- plugin matrix: embedded by default; can be overridden in future (no CLI flag yet)
Pre-restore validation gate for a single config.
pfopn-convert verify <FILE> [--to <pfsense|opnsense>] [--target-version <VERSION>] [--format <text|json>] [--strict] [--verbose]- exits non-zero when hard errors are found
- checks required sections and internal reference integrity for OpenVPN/IPsec dependencies
- checks interface/bridge integrity:
- missing interface references in rules/gateways/static routes
- empty bridge members
- checks rule reference integrity:
- missing alias references in filter rules
- missing gateway references in filter/static route entries
- missing schedule references in filter rules (warning)
- checks firewall rule signature collisions:
- duplicate rule signatures (warning)
- default-rule overlaps with custom signatures (warning)
- checks WireGuard readiness:
- errors if WireGuard appears enabled but no
wireguard/tun_wg*interface assignment exists
- errors if WireGuard appears enabled but no
- warns on unsupported plugins and target compatibility gaps
--target-versionoverrides profile selection for expected-schema checks (<exact>.toml-><major>.toml->default.toml)--strictalso fails on warnings--profiles-dir <path>: override embedded profiles with files from<path>/<platform>/<version>.toml--verbose: show profile source (Using profiles: ...in text mode).
Go/no-go pre-restore check with explicit PASS/FAIL items.
pfopn-convert migrate-check <FILE> --to <pfsense|opnsense> [--target-version <VERSION>] [--format <text|json>] [--strict] [--verbose]- combines scan + verify checks into one report
- fails non-zero when required checks fail
- reports conversion-style counts (
interfaces,bridges,aliases,rules,routes,vpns) - includes advisory expected-schema baseline checks from profiles:
pfopn-convert/profiles/pfsense/default.tomlpfopn-convert/profiles/opnsense/default.toml
--target-versionoverrides profile selection (<exact>.toml-><major>.toml->default.toml)--strictalso fails when warnings exist--profiles-dir <path>: override embedded profiles with files from<path>/<platform>/<version>.toml--verbose: show profile + mapping sources (Using profiles: ...,Using mappings: ...in text mode).
Current support level by area:
| Area | Status |
|---|---|
system |
partial |
interfaces |
supported-with-checks |
filter / nat |
partial |
aliases |
supported |
dhcpd / dhcpdv6 |
supported-with-checks |
openvpn |
supported-with-checks |
ipsec |
supported-with-checks |
wireguard |
supported-with-checks |
tailscale |
supported-with-checks |
gateways |
supported-with-checks |
staticroutes |
supported-with-checks |
ifgroups |
supported-with-checks |
| packages/plugins (general) | partial |
scan is the authoritative first-run readiness report for what is supported vs requires manual review in a given file.
Mappings and plugin matrix are embedded by default.
You can override section mappings with --mappings-file or --mappings-dir.
--mappings-file and --mappings-dir are mutually exclusive.
Profiles can be overridden with --profiles-dir.
Plain English:
- “Mappings” are just a small list that tells the tool how section names line up between pfSense and OPNsense.
- “Profiles” are simple rules for what sections/fields must exist for a given platform/version.
- Inventory sections and naming gaps:
pfopn-convert sections pfsense.xml opnsense.xml- Get high-level and per-section counts:
pfopn-convert diff pfsense.xml opnsense.xml --summary --section-summary- Export machine-readable report and plan:
pfopn-convert diff pfsense.xml opnsense.xml --format json --plan /tmp/plan.json > /tmp/report.json- Produce safe insert-only merged output for review:
pfopn-convert diff pfsense.xml opnsense.xml --output /tmp/merged.xml --merge-to rightDiff counts:
modified: same path exists in both, values differonly_left: path exists only in first fileonly_right: path exists only in second filestructural: structure/tag mismatch
Analysis counts:
insert_left_to_right: safe insert candidateinsert_right_to_left: safe insert candidateconflict_manual: requires manual or handler-driven mappingnoop: no action required
Backend metadata:
- summary output includes
left_backend,right_backend, andbackend_transition inspect --detectincludesversion_source,version_confidence,dhcp_backend, andbackend_reason
If this tool saves you time, feel free to buy me a coffee: