Network automation is no longer optional. It's on the CCNA exam, it's a dedicated Cisco certification track, and it's what separates senior engineers from junior ones in job interviews.
The good news: you don't need to be a software developer. If you can configure a router via CLI, you can automate it with Python. This lab gets you from zero to your first working automation script.
What You Need
- A network lab with SSH-enabled devices (routers or switches)
- Python 3 installed
- The Netmiko library (
pip install netmiko)
To generate a lab with SSH-ready devices:
Build a network automation lab with 3 Cisco routers running OSPF.
All devices must be accessible via SSH with username admin and password cisco123.
Step 1: Connect to a Router
The most basic automation task — connect to a device and run a show command:
from netmiko import ConnectHandler
device = {
"device_type": "cisco_ios",
"host": "10.0.1.1",
"username": "admin",
"password": "cisco123",
}
connection = ConnectHandler(**device)
output = connection.send_command("show ip interface brief")
print(output)
connection.disconnect()Run this script and you'll see the same output as if you typed the command manually:
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 10.0.1.1 YES manual up up
GigabitEthernet0/1 192.168.1.1 YES manual up up
That's it — you just automated your first network task. The script connects via SSH, runs the command, captures the output, and disconnects.
Step 2: Connect to Multiple Devices
The real power of automation is doing the same thing across many devices at once:
from netmiko import ConnectHandler
devices = [
{
"device_type": "cisco_ios",
"host": "10.0.1.1",
"username": "admin",
"password": "cisco123",
},
{
"device_type": "cisco_ios",
"host": "10.0.1.2",
"username": "admin",
"password": "cisco123",
},
{
"device_type": "cisco_ios",
"host": "10.0.1.3",
"username": "admin",
"password": "cisco123",
},
]
for device in devices:
connection = ConnectHandler(**device)
hostname = connection.send_command("show run | include hostname")
version = connection.send_command("show version | include uptime")
print(f"{hostname}")
print(f" {version}")
print()
connection.disconnect()Output:
hostname R1
R1 uptime is 2 hours, 15 minutes
hostname R2
R2 uptime is 2 hours, 14 minutes
hostname R3
R3 uptime is 2 hours, 13 minutes
Three devices checked in seconds. Manually, that's three SSH sessions, three
showcommands, three disconnects. With automation, it's one script run.
Step 3: Push Configuration Changes
Reading is useful. Writing configs is where automation saves serious time:
from netmiko import ConnectHandler
device = {
"device_type": "cisco_ios",
"host": "10.0.1.1",
"username": "admin",
"password": "cisco123",
}
config_commands = [
"interface Loopback99",
"ip address 99.99.99.1 255.255.255.255",
"description Created by automation",
"no shutdown",
]
connection = ConnectHandler(**device)
output = connection.send_config_set(config_commands)
print(output)
# Verify the change
result = connection.send_command("show ip interface brief | include Loopback99")
print(f"\nVerification: {result}")
connection.disconnect()send_config_set() enters config mode, pushes each command, and exits config mode automatically. No need to type configure terminal or end.
Step 4: Backup Configs Across All Devices
A practical script every network team needs — back up running configs to files:
from netmiko import ConnectHandler
from datetime import datetime
devices = [
{"device_type": "cisco_ios", "host": "10.0.1.1", "username": "admin", "password": "cisco123"},
{"device_type": "cisco_ios", "host": "10.0.1.2", "username": "admin", "password": "cisco123"},
{"device_type": "cisco_ios", "host": "10.0.1.3", "username": "admin", "password": "cisco123"},
]
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
for device in devices:
connection = ConnectHandler(**device)
hostname = connection.send_command("show run | include hostname").split()[-1]
config = connection.send_command("show running-config")
filename = f"{hostname}_{timestamp}.txt"
with open(filename, "w") as f:
f.write(config)
print(f"Saved {filename}")
connection.disconnect()Output:
Saved R1_20260316_1430.txt
Saved R2_20260316_1430.txt
Saved R3_20260316_1430.txt
Three routers backed up in seconds. Schedule this to run daily and you have automated config backups.
Step 5: OSPF Neighbor Check Script
A more practical example — verify OSPF neighbors across all routers and flag any issues:
from netmiko import ConnectHandler
devices = [
{"device_type": "cisco_ios", "host": "10.0.1.1", "username": "admin", "password": "cisco123"},
{"device_type": "cisco_ios", "host": "10.0.1.2", "username": "admin", "password": "cisco123"},
{"device_type": "cisco_ios", "host": "10.0.1.3", "username": "admin", "password": "cisco123"},
]
for device in devices:
connection = ConnectHandler(**device)
hostname = connection.send_command("show run | include hostname").split()[-1]
neighbors = connection.send_command("show ip ospf neighbor")
if "FULL" in neighbors:
full_count = neighbors.count("FULL")
print(f"✅ {hostname}: {full_count} OSPF neighbors FULL")
else:
print(f"❌ {hostname}: No FULL OSPF neighbors - CHECK THIS")
connection.disconnect()Output:
✅ R1: 2 OSPF neighbors FULL
✅ R2: 2 OSPF neighbors FULL
✅ R3: 2 OSPF neighbors FULL
This is the kind of script that runs as a health check after maintenance windows.
Multi-Vendor: Juniper Example
Netmiko supports multiple vendors. Here's the same pattern for a Juniper device:
from netmiko import ConnectHandler
juniper_device = {
"device_type": "juniper_junos",
"host": "10.0.2.1",
"username": "admin",
"password": "juniper123",
}
connection = ConnectHandler(**juniper_device)
output = connection.send_command("show interfaces terse")
print(output)
connection.disconnect()Change device_type and the commands — the connection logic stays the same. Netmiko handles the SSH differences between vendors.
Common Mistakes
1. SSH not enabled on the device
Netmiko connects via SSH. If your router only has telnet or console access, the connection will fail. Make sure SSH is configured:
R1(config)# hostname R1
R1(config)# ip domain-name lab.local
R1(config)# crypto key generate rsa modulus 2048
R1(config)# line vty 0 4
R1(config-line)# transport input ssh
R1(config-line)# login local
R1(config)# username admin privilege 15 secret cisco1232. Wrong device_type
cisco_ios is for IOS. If your device runs IOS-XE, IOS-XR, or NX-OS, you need a different device type. Check Netmiko's documentation for the full list.
3. Not handling exceptions
In production, devices might be unreachable. Wrap connections in try/except:
from netmiko import ConnectHandler
from netmiko.exceptions import NetmikoTimeoutException
try:
connection = ConnectHandler(**device)
output = connection.send_command("show version")
except NetmikoTimeoutException:
print(f"Could not connect to {device['host']}")What's Next
Once you're comfortable with Netmiko basics:
- TextFSM / Genie parsing — parse CLI output into structured data instead of raw text
- Ansible — declarative automation (describe the desired state, let Ansible figure out the commands)
- NAPALM — vendor-neutral library for config management and operational data
- REST APIs — interact with modern network controllers programmatically
For more on how automation fits into the CCNA, see CCNA Automation: What Changed in 2026. Or practice routing fundamentals in CCNP labs.
Ready to automate? Get started with NetPilot — build a lab with SSH-ready devices and start writing automation scripts in minutes.