NSX Migration Tool
A Python script for exporting and importing NSX Distributed Firewall (DFW) rules, Security Groups, and VM Tags between NSX Manager instances running version 3.x or 4.x.
No third-party libraries required — uses Python standard library only.
Requirements
- Python 3.6 or later
- Network access to the NSX Manager API (port 443)
- NSX credentials with at least Enterprise Admin or Security Admin role
Supported NSX Versions
- NSX-T 3.x
- NSX-T 4.x
- NSX 9.x (including VCF-mode deployments)
What Gets Exported / Imported
| Resource | Export Source | Import Target | Notes |
|---|---|---|---|
| Tags | /api/v1/fabric/virtual-machines |
?action=update_tags |
Stored as VM tag assignments, not standalone objects |
| Security Groups | /policy/api/v1/infra/domains/default/groups |
Same path via PATCH | Full group definitions including membership criteria |
| DFW Rules | /policy/api/v1/infra/domains/default/security-policies |
Same path via PATCH | All policies and their rules, preserving order |
Output Files (Export)
| File | Default Name | Contents |
|---|---|---|
| Tags | tags.json |
Unique tag list + per-VM assignment records |
| Security Groups | sec_groups.json |
Full group definitions |
| DFW Rules | dfw_rules.json |
Policies with embedded _rules arrays |
Usage
python nsx_migrate.py <action> --host <NSX_IP> -u <username> -p <password> [options]
Actions
| Action | Description |
|---|---|
export |
Export all resources to JSON files |
import |
Import all resources in order: Tags → Security Groups → DFW Rules |
import-tags |
Import only VM tag assignments |
import-groups |
Import only security groups |
Arguments
Connection
| Argument | Short | Required | Default | Description |
|---|---|---|---|---|
--host |
Yes | NSX Manager hostname or IP address | ||
--username |
-u |
Yes | NSX Manager username | |
--password |
-p |
Yes | NSX Manager password | |
--no-verify-ssl |
No | False |
Disable SSL certificate verification (for self-signed certs) |
Source context (export)
| Argument | Required | Default | Description |
|---|---|---|---|
--src-org |
No | default |
Source VCF org name |
--src-project |
No | (none) | Source VCF project name — omit to use the default infra space |
--src-vpc |
No | (none) | Source VPC name within the project — requires --src-project |
Destination context (import)
| Argument | Required | Default | Description |
|---|---|---|---|
--dst-org |
No | default |
Destination VCF org name |
--dst-project |
No | (none) | Destination VCF project name — omit to use the default infra space |
--dst-vpc |
No | (none) | Destination VPC name within the project — requires --dst-project |
Import behaviour
| Argument | Required | Default | Description |
|---|---|---|---|
--conflict |
No | overwrite |
Conflict handling mode — see below |
--merge-tags |
No | False |
Merge imported tags with existing VM tags instead of replacing |
File paths
| Argument | Required | Default | Description |
|---|---|---|---|
--dfw-rules-file |
No | dfw_rules.json |
Path to DFW rules JSON file |
--sec-groups-file |
No | sec_groups.json |
Path to security groups JSON file |
--tags-file |
No | tags.json |
Path to tags JSON file |
Project and VPC Context
NSX 9.x running in VCF mode supports multi-tenancy through Projects and VPCs. The script resolves API paths based on the context arguments provided.
| Context | API path used |
|---|---|
| No project/VPC (default) | /policy/api/v1/infra/domains/default/... |
| Project only | /policy/api/v1/orgs/{org}/projects/{project}/infra/domains/default/... |
| Project + VPC | /policy/api/v1/orgs/{org}/projects/{project}/vpcs/{vpc}/... |
Source and destination contexts are independent. You can export from a project and import to a different project, to a VPC, or to the default space — in any combination.
Note: VM tag lookups always use the global fabric API (
/api/v1/fabric/virtual-machines) regardless of project or VPC context. Tags are a fabric-level concept and are not scoped to projects or VPCs.
Conflict Handling (--conflict)
Controls what happens when an object already exists on the destination NSX.
| Mode | Behavior |
|---|---|
overwrite |
Always write the object, replacing any existing version (default) |
skip |
Leave existing objects untouched; only import objects that do not exist |
prompt |
Ask interactively for each conflict |
Prompt Mode Keys
When --conflict prompt is used, the following keys are accepted per conflict:
| Key | Action |
|---|---|
y |
Yes — overwrite this object |
n |
No — skip this object |
a |
Yes to all — overwrite this and all remaining conflicts without prompting |
Note: In non-interactive environments (piped input, CI/CD),
promptmode defaults to skip when stdin is unavailable.
Tag Merge (--merge-tags)
Applies to import and import-tags actions only.
| Mode | Behavior |
|---|---|
| Without flag | Replace all tags on the VM with the imported set (existing tags are removed) |
--merge-tags |
Union of imported tags and existing tags — no tags are removed, only new ones added |
Tag deduplication is based on the (scope, tag) pair.
VM Resolution (Cross-vCenter)
When importing tags, the script resolves VMs on the destination using a two-step lookup:
- Primary —
external_id: Matches the vCenter MoRef ID. Works when both environments use the same vCenter or the IDs happen to align. - Fallback —
display_name: Matches by VM name. Used automatically when theexternal_idlookup returns no results.
If multiple VMs share the same display name, the first result is used and a warning is printed. If neither lookup finds the VM, a warning is logged and the assignment is skipped — the import continues.
Examples
Export from source NSX
python nsx_migrate.py export \
--host 192.168.1.10 \
--username admin \
--password VMware1! \
--no-verify-ssl
Produces tags.json, sec_groups.json, and dfw_rules.json in the current directory.
Full import to destination NSX (overwrite existing)
python nsx_migrate.py import \
--host 192.168.2.10 \
--username admin \
--password VMware1! \
--no-verify-ssl
Full import — skip objects that already exist
python nsx_migrate.py import \
--host 192.168.2.10 \
--username admin \
--password VMware1! \
--no-verify-ssl \
--conflict skip
Full import — prompt on each conflict
python nsx_migrate.py import \
--host 192.168.2.10 \
--username admin \
--password VMware1! \
--no-verify-ssl \
--conflict prompt
Import only tags, merging with existing
python nsx_migrate.py import-tags \
--host 192.168.2.10 \
--username admin \
--password VMware1! \
--no-verify-ssl \
--merge-tags
Import only security groups, skip existing
python nsx_migrate.py import-groups \
--host 192.168.2.10 \
--username admin \
--password VMware1! \
--no-verify-ssl \
--conflict skip
Export from a VCF project
python nsx_migrate.py export \
--host 192.168.1.10 -u admin -p VMware1! --no-verify-ssl \
--src-project my-project
Export from a VPC inside a project
python nsx_migrate.py export \
--host 192.168.1.10 -u admin -p VMware1! --no-verify-ssl \
--src-project my-project --src-vpc my-vpc
Import into a specific project
python nsx_migrate.py import \
--host 192.168.2.10 -u admin -p VMware1! --no-verify-ssl \
--dst-project my-project --conflict skip
Import into a VPC
python nsx_migrate.py import \
--host 192.168.2.10 -u admin -p VMware1! --no-verify-ssl \
--dst-project my-project --dst-vpc my-vpc
Cross-project migration on the same NSX host
# Step 1 — export from project-a
python nsx_migrate.py export \
--host 192.168.1.10 -u admin -p VMware1! --no-verify-ssl \
--src-project project-a
# Step 2 — import to project-b
python nsx_migrate.py import \
--host 192.168.1.10 -u admin -p VMware1! --no-verify-ssl \
--dst-project project-b --conflict skip
Use custom file names
# Export to custom paths
python nsx_migrate.py export \
--host 192.168.1.10 -u admin -p VMware1! \
--dfw-rules-file backup_dfw.json \
--sec-groups-file backup_groups.json \
--tags-file backup_tags.json
# Import from custom paths
python nsx_migrate.py import \
--host 192.168.2.10 -u admin -p VMware1! \
--dfw-rules-file backup_dfw.json \
--sec-groups-file backup_groups.json \
--tags-file backup_tags.json
Import Order
When running a full import, resources are always applied in the following order to ensure dependencies are satisfied:
1. Tags — VMs must be present; no dependency on groups or rules
2. Security Groups — may reference tags in dynamic membership criteria
3. DFW Rules — policies reference security groups as sources/destinations
Output / Status Codes
Each resource line during import prints one of the following prefixes:
| Prefix | Meaning |
|---|---|
OK |
Successfully written to destination |
SKIP |
Object already exists and was left untouched (skip or prompt=no) |
WARNING |
Non-fatal issue (e.g. VM not found for tag assignment) |
FAIL |
API call failed — error detail printed on the same line |
A summary line is printed after each resource type showing total counts.
Notes and Limitations
- Tags are not standalone objects in NSX. They exist only as attributes on VMs. The export captures tag assignments per VM; the import re-applies them. VMs must already exist and be visible to NSX on the destination.
- Security group membership expressions are exported as-is. If expressions reference objects that do not exist on the destination (e.g. segments, tags not yet applied), the group will be created but membership may be empty until those dependencies exist.
- DFW rule references (source/destination groups, services) are exported by path/ID. If referenced groups or services do not exist on the destination, rules will be created but may not match traffic as expected. Importing groups before rules (the default order) mitigates this for group references.
- Read-only fields (
_revision,_create_time,_last_modified_*, etc.) are stripped before import so PATCH requests do not fail due to server-managed metadata. - VCF deployments (NSX 9.x): VCF-mode NSX adds extra fields to objects (
remote_path,unique_id,realization_id,owner_id) that reference the source environment's org/project context. These are automatically stripped before import so they are not carried across to the destination. - The script operates on the default domain only (
/infra/domains/default).