One of the first things any network engineer learns (usually the hard way) is: always have recent backups of your running configurations.
Manual copy running-config tftp://... or show run → paste into Notepad works… until you manage 20+ devices.
After that you either forget, make mistakes, or spend hours doing repetitive work.
I recently put together a small Python script using Paramiko to automate full running-config backups from Cisco devices (switches + routers like ISR4451, Catalyst 1300, etc.).
It turned out useful enough that I thought I’d share the core version + some lessons learned.
What the script does
- Reads a list of IP addresses from a text file (one per line)
- Connects via SSH using Paramiko
- Enters enable mode (if needed)
- Disables paging (
terminal length 0) - Captures
show running-config - Removes unwanted sections (in my case: a specific ACL named
test_acl) - Strips ANSI escape codes and extra blank lines
- Saves clean timestamped .txt files in a local folder
- Supports multi-threading so you can backup many devices in parallel
Quick sample below:
Python
import paramiko
import re
import os
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
# ── Credentials & Paths ──
USERNAME = "your-username"
PASSWORD = "your-password"
IPS_FILE = r"D:\python\devices.txt"
BACKUP_DIR = r"D:\python\backups"
os.makedirs(BACKUP_DIR, exist_ok=True)
MAX_WORKERS = 4
def remove_test_acl_block(config_text):
lines = config_text.splitlines()
result = []
in_target_acl = False
for line in lines:
stripped = line.strip()
if re.match(r'^ip access-list (extended|standard) test_acl\b', stripped):
in_target_acl = True
continue
if in_target_acl:
if stripped and not line.startswith(' '):
in_target_acl = False
result.append(line.rstrip())
continue
result.append(line.rstrip())
cleaned = '\n'.join(result)
cleaned = re.sub(r'\n\s*\n+', '\n\n', cleaned).strip()
return cleaned
def backup_device(ip):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ts = datetime.now().strftime("%Y%m%d_%H%M")
filename = f"{ip.replace('.', '_')}_config_{ts}.txt"
path = os.path.join(BACKUP_DIR, filename)
try:
client.connect(ip, username=USERNAME, password=PASSWORD, timeout=12)
shell = client.invoke_shell()
time.sleep(1.5)
# Disable paging
shell.send("terminal length 0\n")
time.sleep(1)
while shell.recv_ready():
shell.recv(8192)
# Get config
shell.send("show running-config\n")
time.sleep(2)
output = ""
start = time.time()
while time.time() - start < 60:
if shell.recv_ready():
chunk = shell.recv(16384).decode(errors="ignore")
output += chunk
if re.search(r'[>#]\s*$', chunk):
break
time.sleep(0.2)
# Clean up
clean = re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', '', output)
filtered = remove_test_acl_block(clean)
# Save
with open(path, "w", encoding="utf-8") as f:
f.write(f"Backup — {ip} — {ts}\n{'='*50}\n\n{filtered}\n")
print(f"OK {ip} → {filename}")
return path
except Exception as e:
print(f"FAIL {ip}: {e}")
return None
finally:
client.close()
# ── Main ──
if __name__ == "__main__":
if not os.path.exists(IPS_FILE):
print("IP file missing")
exit(1)
with open(IPS_FILE) as f:
ips = [l.strip() for l in f if l.strip()]
print(f"Backing up {len(ips)} devices...\n")
successes = 0
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as ex:
futures = {ex.submit(backup_device, ip): ip for ip in ips}
for future in as_completed(futures):
if future.result():
successes += 1
print(f"\nDone: {successes}/{len(ips)} successful")Tips:
- Enable mode If your devices require enable, add enable\n + password right after connect. (I left it commented in the version above since my lab devices use privilege 15 on login.)
- Timeouts Large configs (>30k lines) can take 30–60 seconds. I set generous timeouts.
- ACL removal The remove_test_acl_block() function is very simple — it stops at the next non-indented line. Works for most cases, but if you have nested objects inside the ACL it might cut too early. (Very rare in standard ACLs.)
- Security Never commit this script with real passwords. Use getpass.getpass() or environment variables / keyring in production.