Config Snapshots
EnterpriseCapture, store, and compare device configurations with versioned config records, SHA-256 change detection, and fleet-wide snapshot collections.
Overview
Config snapshots are part of the NetStacks Enterprise controller. Every endpoint on this page lives under the controller API and requires an operator JWT.
Config snapshots capture the configuration of network devices at a specific point in time. Each capture is stored as a versioned device config record in PostgreSQL with a SHA-256 content hash for change detection, plus metadata such as the source format, transport, and the credential used.
Snapshots serve several purposes:
- Configuration backup — Preserve known-good configs for disaster recovery and rollback reference.
- Change detection — Compare two stored versions to see exactly what changed between captures.
- Compliance auditing — Prove device configs matched a standard at a given date and version.
- Fleet-wide captures — A snapshot collection captures configs from many devices in one operation, ideal for pre/post maintenance-window verification.
A config snapshot is a collection-level record that groups a single capture operation across many devices and tracks total / successful / failed device counts. Each device that is captured produces an individual device config version (an auto-incrementing version per device) linked back to the snapshot by snapshot_id. Think of a snapshot as "capture configs from these devices now" and a device config version as the resulting config text from one device.
How It Works
Config collection process
When NetStacks captures a device configuration over SSH/CLI, it follows this process:
- Resolves the device's credential, decrypts it from the encrypted vault, and connects to
device.host:device.port. - Sends the platform-specific paging-disable command (for example
terminal length 0on Cisco IOS, orterminal pager 0on ASA) followed byshow running-config. - Captures the command output and stores the cleaned configuration text.
- Computes a
SHA-256hash of the config text for change detection. - Writes a new device config version with metadata:
config_format(e.g.cli),config_hash, an auto-incrementedversion,source,pulled_via(e.g.ssh),created_at, andsnapshot_id.
Hash-based change detection
Every device config version stores a config_hash (SHA-256) of the configuration text. To detect change, NetStacks compares hashes between two versions. Identical hashes mean the config is unchanged regardless of when each version was captured; differing hashes mean the config changed. The version-diff endpoint uses this directly — if the two versions' hashes match it returns identical: true and skips diff generation.
Snapshot collections
A snapshot collection targets multiple devices at once. When you create a snapshot you supply:
- name — A descriptive label (e.g. "Pre-maintenance 2026-03-10").
- device_filter — Which devices to include. The collect handler reads
device_filter.siteanddevice_filter.device_typeonly. - snapshot_type — One of
manual,scheduled,pre_change, orpost_change(defaults tomanual).
For snapshot collection, the filter honours site and device_type only. Tag-based filtering is not applied when resolving snapshot targets — passing a tag key has no effect. To capture an arbitrary set of devices regardless of site/type, use the bulk config-pull endpoint instead (see Code Examples).
Collection runs concurrently behind a semaphore (default 5 simultaneous devices). Each device is collected independently — if one device fails, the others continue. When the run finishes the snapshot records successful and failed counts and a final status.
Snapshot lifecycle & status
A snapshot moves through these statuses:
pending— created, collection not yet started.in_progress— collection running (set when you call/collect).complete— all targeted devices succeeded (failed count is 0).partial— some devices succeeded and some failed.failed— every targeted device failed.
Credential resolution
When collecting configs, NetStacks resolves the credential for each device in priority order:
- Per-request credential override (
credential_idin the collect call). - Device default credential (
default_credential_idon the device record). - Global collection settings default credential.
If no credential is found at any level, that device is recorded as a failed config with the message No credential configured; the rest of the collection continues.
Step-by-Step Guide
Capture a single device config
- Open the device in the controller admin UI and trigger a config pull, or call the pull API directly (see Code Examples).
- NetStacks connects, runs
show running-config, and stores a new device config version with a fresh SHA-256 hash. - The new version appears in the device's config history with its timestamp, version number, format, and hash.
Create a snapshot collection across multiple devices
- Create a snapshot record with a descriptive name.
- Set
device_filterwithsiteand/ordevice_typeto select targets (for example, all devices at sitedc-eastof typecisco_ios). - Trigger
collect. NetStacks resolves matching devices, sets the snapshot toin_progress, and captures each device concurrently. - Poll the snapshot until status becomes
complete,partial, orfailed. The record shows total, successful, and failed device counts plus anerrorsarray.
Create a snapshot before a maintenance window (type pre_change) and another afterward (type post_change). Then compare the two device config versions captured for any device with the version-diff endpoint (see Config Diff & History) to verify only intended changes were made.
Schedule automatic snapshots
- Create a scheduled task that triggers a config-backup snapshot on a cron schedule (for example daily at 02:00).
- Define the device targets via the snapshot's
device_filter. - The task creates a snapshot with
snapshot_type: "scheduled"and triggers collection at the scheduled time.
See Scheduled Tasks for the cron and task model.
Browse config history
- List a device's config versions via
GET /api/config/devices/{id}/configs. - Fetch full text for the latest version (
/configs/latest) or a specific version (/configs/{version}). - Compare any two versions with the version-diff endpoint to see a unified diff with addition and deletion counts.
Code Examples
Create a snapshot
curl -X POST http://localhost:3000/api/devices/snapshots \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Pre-maintenance 2026-03-10",
"description": "Capture all DC East cisco_ios configs before OSPF migration",
"snapshot_type": "pre_change",
"device_filter": {
"site": "dc-east",
"device_type": "cisco_ios"
}
}'
# 201 Created -> { "snapshot": { "id": "...", "status": "pending", ... } }Trigger config collection
# Start collection for a snapshot (runs in the background).
# credential_id and timeout_seconds are optional.
curl -X POST http://localhost:3000/api/devices/snapshots/${SNAPSHOT_ID}/collect \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timeout_seconds": 60
}'
# 202 Accepted:
# {
# "message": "Config collection started",
# "snapshot_id": "...",
# "devices_queued": 24
# }Poll snapshot status
curl -s http://localhost:3000/api/devices/snapshots/${SNAPSHOT_ID} \
-H "Authorization: Bearer ${TOKEN}" | jq '.snapshot | {
name, status, total_devices, successful_devices, failed_devices,
started_at, completed_at
}'
# {
# "name": "Pre-maintenance 2026-03-10",
# "status": "complete",
# "total_devices": 24,
# "successful_devices": 24,
# "failed_devices": 0,
# "started_at": "2026-03-10T14:32:01Z",
# "completed_at": "2026-03-10T14:33:18Z"
# }List the device configs captured by a snapshot
curl -s http://localhost:3000/api/devices/snapshots/${SNAPSHOT_ID}/backups \
-H "Authorization: Bearer ${TOKEN}" | jq '.backups[] | {
device_name, version, backup_method, status, config_hash, backed_up_at
}'
# Note: this list endpoint returns size_bytes and line_count as 0
# (they are not stored in the summary). Fetch full text from the
# config endpoints below to compute size or line count.Capture a single device on demand (config pull)
# Pull a fresh config for one device, storing a new version.
curl -X POST http://localhost:3000/api/config/devices/${DEVICE_ID}/pull \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json"
# Bulk pull for an explicit list of devices (creates a snapshot
# record so the run appears in the Job Monitor):
curl -X POST http://localhost:3000/api/config/devices/bulk-pull \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"device_ids": [
"11111111-1111-1111-1111-111111111111",
"22222222-2222-2222-2222-222222222222"
]
}'
# Bulk-pull response:
# { "succeeded": 2, "failed": 0, "errors": [] }Browse a device's config versions
# List version history (summaries, no config text)
curl -s "http://localhost:3000/api/config/devices/${DEVICE_ID}/configs?limit=10" \
-H "Authorization: Bearer ${TOKEN}" | jq '.configs[] | {
version, config_format, config_hash, pulled_via, status, created_at
}'
# Fetch the latest version with full text
curl -s http://localhost:3000/api/config/devices/${DEVICE_ID}/configs/latest \
-H "Authorization: Bearer ${TOKEN}" | jq '{ version, config_format, config_hash }'
# Fetch a specific version with full text
curl -s http://localhost:3000/api/config/devices/${DEVICE_ID}/configs/7 \
-H "Authorization: Bearer ${TOKEN}" | jq '.config_text' -rCompare two stored versions (unified diff)
curl -s "http://localhost:3000/api/config/devices/${DEVICE_ID}/diff-versions?old_version=6&new_version=7" \
-H "Authorization: Bearer ${TOKEN}" | jq '{
old_version, new_version, identical, additions, deletions
}'
# {
# "old_version": 6,
# "new_version": 7,
# "identical": false,
# "additions": 3,
# "deletions": 1
# }
# The full unified diff text is in the .diff field.Example captured config (Cisco IOS)
! Last configuration change at 14:32:07 UTC Tue Mar 10 2026
version 17.9
service timestamps debug datetime msec
service timestamps log datetime msec
service password-encryption
!
hostname core-rtr-01
!
aaa new-model
aaa authentication login default local
aaa authorization exec default local
!
ip domain name dc-east.company.com
ip name-server 10.0.0.53
ip name-server 10.0.0.54
!
interface GigabitEthernet0/0/0
description Uplink to dist-sw-01
ip address 10.1.0.1 255.255.255.252
ip ospf 1 area 0
no shutdown
!
interface GigabitEthernet0/0/1
description Uplink to dist-sw-02
ip address 10.1.0.5 255.255.255.252
ip ospf 1 area 0
no shutdown
!
router ospf 1
router-id 10.255.0.1
network 10.1.0.0 0.0.0.3 area 0
network 10.1.0.4 0.0.0.3 area 0
passive-interface default
no passive-interface GigabitEthernet0/0/0
no passive-interface GigabitEthernet0/0/1
!
ip access-list extended MGMT-ACCESS
permit tcp 10.0.0.0 0.0.0.255 any eq 22
permit tcp 10.0.0.0 0.0.0.255 any eq 443
deny ip any any log
!
line con 0
logging synchronous
line vty 0 4
access-class MGMT-ACCESS in
transport input ssh
!
endQuestions & Answers
- Q: How do I take a config snapshot?
- A: For one device, pull its config with
POST /api/config/devices/{id}/pull, which stores a new version. For many devices, create a snapshot withPOST /api/devices/snapshots, set adevice_filter(site and/or device_type), then triggerPOST /api/devices/snapshots/{id}/collect. To capture an explicit list of devices in one job, usePOST /api/config/devices/bulk-pull. - Q: How does change detection work?
- A: Each device config version stores a SHA-256
config_hashof its text. To see whether a config changed between two versions, callGET /api/config/devices/{id}/diff-versions?old_version=N&new_version=M. If the hashes match it returnsidentical: true; otherwise it returns a unified diff withadditionsanddeletionscounts. - Q: Does the snapshot device_filter support tags?
- A: No. The snapshot collect handler reads only
device_filter.siteanddevice_filter.device_type. Atagkey is ignored. To capture an arbitrary device set, list the device IDs and callPOST /api/config/devices/bulk-pull. - Q: What happens if a device is unreachable during collection?
- A: Collection continues for every other device. The unreachable device is recorded as a failed config with an error message, and the snapshot's
errorsarray andfailed_devicescount are updated. If at least one device also succeeded, the final status ispartial; if every device failed it isfailed. - Q: How long are config versions retained?
- A: Device config versions are retained in PostgreSQL until you delete them or delete the parent snapshot. There is no built-in automatic purge. For large fleets, monitor PostgreSQL disk usage and apply your own retention policy to meet compliance requirements.
- Q: Can I export a stored config?
- A: Yes. Fetch the latest version via
GET /api/config/devices/{id}/configs/latestor a specific version viaGET /api/config/devices/{id}/configs/{version}and readconfig_text. Script these calls to export configs into a filesystem or version-control repository.
Troubleshooting
Collection fails with SSH timeout
The default per-device collection timeout is 30 seconds. Large configs (for example firewalls with thousands of ACL rules) can exceed it. Increase it with the timeout_seconds field in the collect call, or raise the global collection.timeout_seconds setting.
curl -X POST http://localhost:3000/api/devices/snapshots/${SNAPSHOT_ID}/collect \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"timeout_seconds": 120}'"No credential configured" for a device
No credential was found at any level for that device: no credential_id in the collect request, no default_credential_id on the device, and no global collection default. Assign a default credential to the device, set a global default, or pass credential_id in the collect call.
Captured config looks malformed or wrong
If output looks malformed, the device type / platform is probably wrong, so the collector sends the wrong paging and show running-config commands. Verify the device's type matches the real platform (see Device Types) and capture again.
Snapshot reports "No devices match the filter"
The collect call returns 400 Bad Request when the device_filter resolves to zero devices. Confirm the site and device_type values exactly match existing devices — and remember that tag filters are ignored for snapshots, so a tag-only filter resolves to every device, not a subset.
Snapshot stuck in in_progress
Collection runs in the background. A snapshot stays in_progress until every queued device finishes. With slow or unreachable devices, allow up to timeout_seconds per device (and retries) before the run completes and the status moves to complete, partial, or failed.
Related Features
Config snapshots integrate with comparison, automation, and device management features:
- Config Diff & History — Compare two stored config versions with the version-diff endpoint
- Bulk Operations — Bulk config pull captures an explicit device list in one job
- Adding Devices — Devices must exist in the inventory before configs can be captured
- Scheduled Tasks — Automate periodic config captures with cron schedules
- Device Types — Platform drivers determine the paging and config-capture commands