Back up device configs with a Netmiko script
PythonCore
Tasks
- Make sure every device accepts SSH from SRV1 with a backup account that lands in privileged mode.
- Write
inventory.ymlwith the three devices. - Write
backup.pythat backs up all devices in parallel intobackups/<date>/<name>.cfg. - Handle authentication failures, unreachable devices and slow output per device without stopping the run.
- Exit with code 1 if any device failed.
Solution
1Device prerequisitesASW1
ASW1 on Cisco IOS ip domain name example.com crypto key generate rsa modulus 2048 ip ssh version 2 username backup privilege 15 secret 0 Backup.2026 line vty 0 15 login local transport input sshRepeat on ASW2 and CORE1. A privilege 15 local user keeps the lab simple. In production use a TACACS+ service account that is authorized only for the show commands the job needs.
2Inventory file
YAML # inventory.yml devices: - name: CORE1 host: 10.0.0.1 device_type: cisco_xe - name: ASW1 host: 10.0.0.11 device_type: cisco_ios - name: ASW2 host: 10.0.0.12 device_type: cisco_ios3The backup script
Python #!/usr/bin/env python3 """Back up the running config of every device in inventory.yml.""" import os import sys from concurrent.futures import ThreadPoolExecutor from datetime import datetime from pathlib import Path import yaml from netmiko import ConnectHandler from netmiko.exceptions import ( NetmikoAuthenticationException, NetmikoTimeoutException, ReadTimeout, ) USERNAME = os.environ["NET_USER"] PASSWORD = os.environ["NET_PASS"] BACKUP_DIR = Path("backups") / datetime.now().strftime("%Y-%m-%d") def backup(dev): params = { "device_type": dev["device_type"], "host": dev["host"], "username": USERNAME, "password": PASSWORD, } try: with ConnectHandler(**params) as conn: config = conn.send_command("show running-config", read_timeout=120) except NetmikoAuthenticationException: return dev["name"], "FAIL auth" except NetmikoTimeoutException: return dev["name"], "FAIL unreachable" except ReadTimeout: return dev["name"], "FAIL read timeout" path = BACKUP_DIR / f"{dev['name']}.cfg" path.write_text(config + "\n") return dev["name"], f"OK {len(config.splitlines())} lines" def main(): with open("inventory.yml") as f: devices = yaml.safe_load(f)["devices"] BACKUP_DIR.mkdir(parents=True, exist_ok=True) with ThreadPoolExecutor(max_workers=10) as pool: results = list(pool.map(backup, devices)) failed = 0 for name, status in sorted(results): print(f"{name:<8} {status}") if status.startswith("FAIL"): failed += 1 sys.exit(1 if failed else 0) if __name__ == "__main__": main()Each worker catches its own exceptions and returns a status, so one bad device never stops the others.
read_timeout=120covers large configs; the Netmiko 4 default is 10 seconds.4Run it
Linux export NET_USER=backup read -s -p "Password: " NET_PASS; echo; export NET_PASS python3 backup.pyread -skeeps the password out of shell history. For a cron job, load the variables from a file that only the service account can read, or from a secrets manager.
Verify
SRV1# python3 backup.py; echo "exit code $?"
ASW1 OK 212 lines
ASW2 OK 209 lines
CORE1 OK 1184 lines
exit code 0Every device reports OK and the script exits with 0.
SRV1# ls backups/2026-11-10/ && head -5 backups/2026-11-10/ASW1.cfg
ASW1.cfg ASW2.cfg CORE1.cfg
Building configuration...
Current configuration : 6843 bytes
!
! Last configuration change at 09:12:44 UTC Mon Nov 9 2026 by netopsOne file per device in today's folder, each starting with the device's own config header.
SRV1# NET_PASS=wrong python3 backup.py; echo "exit code $?"
ASW1 FAIL auth
ASW2 FAIL auth
CORE1 FAIL auth
exit code 1Failures are reported per device and the exit code is 1, so cron or CI flags the run.
Mistakes people make
- Leaving
read_timeoutat the default and gettingReadTimeouton the switches with the biggest configs. - Letting one exception escape a worker. With
pool.map, it is raised when you read the results and the whole run dies. - Putting the password in the script or the inventory file.
- Storing backups in a world-readable folder. Configs hold password hashes, SNMP communities and keys.
- Using
cisco_iosfor an NX-OS or Junos device. Pick the rightdevice_typeper platform.
On other platforms
The same idea with NAPALM, which can also strip secrets from the config it returns.
from napalm import get_network_driver
driver = get_network_driver("ios")
with driver("10.0.0.11", USERNAME, PASSWORD) as dev:
running = dev.get_config(retrieve="running", sanitized=True)["running"]Say it in the interview
"The script reads devices from YAML, runs Netmiko in a thread pool, and each worker catches authentication, connection and read timeout errors and returns a status. Configs go to a dated folder, and the exit code is non-zero if anything failed so the scheduler alerts. Next steps would be committing the backups to a private Git repo for history and diffs."