Skip to content

Sync & Delivery

The sync mechanism is how configuration changes propagate from NetDefense to devices. Whenever you modify a snippet, change a template, or reassign an OU, the affected devices need to receive the updated configuration.

NetDefense computes a SHA-256 hash of the expected configuration payload for each device, based on its current OU assignments, templates, snippets, and variables. This hash is compared against the hash stored from the device’s last successful sync.

  • SYNCED — the device’s active configuration matches the current expected hash. No action needed.
  • NOT SYNCED — the hashes differ, meaning configuration changes have been made since the device last synced.

The sync hash is calculated based on the origin policy and kept in the NetDefense database, not on the device itself.

In addition to the hash-based sync state, NetDefense tracks a drift status for each device. Where the sync hash answers “does the device have the latest configuration we sent?”, the drift status answers “is the device’s running configuration still what we last delivered?”.

Drift StatusMeaning
IN_SYNCThe running configuration matches the last-delivered configuration. No action needed.
DRIFTThe running configuration has diverged from the last sync. A manual change was likely made directly on the device.
NEVER_SYNCEDThe device has never received a sync. No baseline to compare against.
UNKNOWNDrift information has not yet been collected for this device.
ERRORThe drift check itself encountered an error on the device.

A DRIFT status does not block syncs — it’s informational. Running ndcli sync apply --device <name> (with or without --force) will push the current expected configuration and clear the drift.

Today, every sync is operator-initiated: configuration only reaches a device when someone runs ndcli sync apply (or the equivalent web action), which queues a sync task that the device executes the next time NDAgent connects to NetDefense. NetDefense doesn’t push changes on its own.

A future release will let devices opt into pulling new configuration on their own — the Auto-Sync column already shown in ndcli sync status is the placeholder for that flag. Until that ships, the column has no effect on behaviour: every sync still requires an explicit trigger.

Terminal window
ndcli sync status
╭───────────────────┬────────────────┬───────────┬───────────┬──────────────┬──────────────╮
│ Device │ OU │ Auto-Sync │ Synced At │ Status │ Drift │
├───────────────────┼────────────────┼───────────┼───────────┼──────────────┼──────────────┤
│ fw-branch-austin │ branch-offices │ Yes │ 1d │ ○ NOT SYNCED │ ⚠ DRIFT │
│ fw-branch-chicago │ branch-offices │ Yes │ 3h │ ● SYNCED │ ✓ IN_SYNC │
│ fw-branch-denver │ branch-offices │ Yes │ 3h │ ● SYNCED │ ✓ IN_SYNC │
│ fw-guest-lobby │ guest-networks │ Yes │ 5m │ ● SYNCED │ ✓ IN_SYNC │
│ fw-hq-primary │ production │ Yes │ 5m │ ● SYNCED │ ✓ IN_SYNC │
│ fw-hq-secondary │ production │ Yes │ 5m │ ● SYNCED │ ✓ IN_SYNC │
│ fw-staging-01 │ staging │ No │ 1h │ ○ NOT SYNCED │ ✓ IN_SYNC │
╰───────────────────┴────────────────┴───────────┴───────────┴──────────────┴──────────────╯
Total: 7 devices

Reading this output:

  • fw-staging-01 shows NOT SYNCED because no one has triggered a sync since its last config change — the policy is updated, the device just hasn’t been told to apply it yet.
  • fw-branch-austin also shows NOT SYNCED, but its last sync was over a day ago and it’s running an older agent version (2.3.9). Worth investigating connectivity or agent freshness before retrying.
  • The other devices are all SYNCED with recent sync times, indicating healthy operations.

Manually push configuration to a specific device:

Terminal window
ndcli sync apply --device fw-staging-01

Or sync all devices in an OU at once:

Terminal window
ndcli sync apply --ou production

You can also use --force to push the configuration even if the hashes already match, which is useful for troubleshooting or to override drifted configurations due to local changes.

Most snippet types behave symmetrically: attaching a snippet creates the entry on the device; detaching it (and triggering a sync) removes the entry on the device. NDAgent identifies its own entries by an ownership marker — a UUID prefix or a name prefix, depending on the snippet type — and on every sync it diffs the device’s managed entries against the desired set from your templates, deleting anything in the former but not the latter.

A few snippet types are asymmetric by design: removing them from a template does not undo what was already applied on the device. These are always singleton configurations (one record per device, no concept of “delete the row”) where the safest default is to stop pushing changes rather than guess what “removed” should mean.

Snippet typeWhat detach doesHow to undo
ZABBIX_SETTINGSDevice keeps the last-applied Zabbix Agent configuration. Sync stops touching Services › Zabbix Agent › Settings.Push a ZABBIX_SETTINGS snippet with "enabled": false to disable the agent, or edit the original values directly in the OPNsense UI.

The companion list snippet types (ZABBIX_USERPARAMETER, ZABBIX_ALIAS) follow the symmetric rule — detach + sync removes them from the device.

For symmetric snippet types — firewall aliases, firewall rules, Unbound host overrides / forwards / aliases / ACLs, Zabbix UserParameters, Zabbix item-key aliases, WireGuard servers/peers — “delete” is a well-defined operation: NDAgent knows which records it created (by UUID prefix or name prefix) and can DELETE each one through the OPNsense REST API.

For a singleton config tree there’s no analogous operation. Imagine detaching ZABBIX_SETTINGS from a template: should that:

  1. Leave the device alone (current behavior) — last-applied settings persist, sync stops touching them.
  2. Reset to OPNsense defaults — NDAgent would have to know the plugin’s defaults, which drift across OPNsense versions.
  3. Disable the service entirely — surprise outage if the template was detached for an unrelated reason.

Option 1 is the conservative default. Detaching a singleton snippet is treated as “stop managing this config from NetDefense” rather than “revert this config on the device”. Disabling the service is an explicit operator action, not a side effect of template reorganization.

If future snippet types introduce additional singleton configurations, they’ll be added to the table above with the same semantics.