Direct Answer
NetBox 3.x JSON backups cannot be restored directly into a 4.x instance. The nb_restore management command loads data via Django's loaddata with transaction wrapping and primary-key preservation, but it performs no schema translation between major versions. NetBox 4.0 (Django 4.2) introduced required fields (Device.role, Interface.type choices, Cable generic foreign keys) and renamed or removed models (Site → Location, RackGroup → Location, Platform split into Manufacturer + DeviceType). A 3.x export references the old model names and primary keys, so loaddata fails with IntegrityError or ValidationError on the first missing column or broken foreign key.
Confirmed Failure Modes
- Missing required fields — 4.x models enforce non-nullable columns that 3.x exports do not contain.
- Broken foreign keys — 3.x JSON references
dcim.Site and dcim.RackGroup primary keys; those tables no longer exist in 4.x.
- ContentType ID mismatch — Custom fields, tags, and journal entries use
ContentType IDs that differ between versions; restored rows point to wrong models.
- Secrets encryption —
Device.secrets_group and other encrypted fields use different key handling; restored secrets become unreadable.
- Plugin model drift — Any plugin that adds models in 3.x but not 4.x (or vice versa) causes
loaddata to fail on missing tables or columns.
The transaction rolls back, but migration records may be left in an ambiguous state, leaving the 4.x database partially loaded and inconsistent.
Documented Upgrade Path
NetBox 4.0 release notes state explicitly: "Backups from NetBox v3.x cannot be restored directly into v4.0. You must upgrade your v3.x instance to v4.0 first, then create a new backup." The supported sequence:
- Restore the 3.x backup into a throwaway 3.x instance (same patch version as the export).
- Run
python manage.py migrate to apply all 3.x → 4.x schema migrations.
- Verify object counts and relationships in the upgraded instance.
- Export a fresh 4.x backup with
python manage.py dumpdata --exclude=contenttypes --exclude=auth.permission --exclude=sessions > backup_v4.json.
- Restore that 4.x backup into the production 4.x environment.
Verification Without API-Only Checks
Compare counts at the database layer before and after migration:
# In the 3.x instance (source)
python manage.py shell -c "from dcim.models import Device, Interface, Cable; print('Device:', Device.objects.count(), 'Interface:', Interface.objects.count(), 'Cable:', Cable.objects.count())"
# After migrate to 4.x (target)
python manage.py shell -c "from dcim.models import Device, Interface, Cable; print('Device:', Device.objects.count(), 'Interface:', Interface.objects.count(), 'Cable:', Cable.objects.count())"
For exhaustive verification, run raw SQL against both databases:
SELECT 'Device' AS model, COUNT(*) FROM dcim_device
UNION ALL SELECT 'Interface', COUNT(*) FROM dcim_interface
UNION ALL SELECT 'Cable', COUNT(*) FROM dcim_cable;
Match every model you care about (sites/locations, racks, prefixes, VLANs, etc.). Relationship integrity (e.g., every Interface still points to a valid Device) is enforced by the migration's foreign-key constraints; if counts match and migrate completes without error, the object graph is consistent.
Version Assumptions & Uncertainty
- Assumes NetBox 3.7.x → 4.0+ (Django 3.2 → 4.2). Behavior may differ for 3.6 or 4.1+ patch releases.
- Custom plugins or local model overrides are not covered; they can introduce additional missing columns or ContentType conflicts.
- If the 3.x backup was produced with
--natural-foreign or --natural-primary, the JSON uses natural keys instead of PKs — this does not resolve the model-rename problem.
One Diagnostic Detail Needed
Does the 3.x export include any plugin-provided models (e.g., netbox_dns, netbox_topology_views)? If yes, list them — their migration path may require separate plugin upgrades before the core 3.x → 4.x migrate step.