Netmiko is one of the first tools a network automation engineer encounters. Built by Kirk Byers, it abstracts the messiness of SSH handling across dozens of vendor platforms into a clean, consistent API.

Installation

1
pip install netmiko

Basic Connection

1
2
3
4
5
6
7
8
9
10
11
12
from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host":        "192.168.1.1",
    "username":    "admin",
    "password":    "cisco123",
}

with ConnectHandler(**device) as conn:
    output = conn.send_command("show ip interface brief")
    print(output)

Always use the with statement — it ensures the connection is properly closed even if an exception occurs.

Supported Platforms

Platform device_type
Cisco IOS / IOS-XE cisco_ios
Cisco IOS-XR cisco_xr
Cisco NX-OS cisco_nxos
Arista EOS arista_eos
Juniper JunOS juniper_junos

Robust Error Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from netmiko import ConnectHandler
from netmiko.exceptions import (
    NetmikoTimeoutException,
    NetmikoAuthenticationException,
)
import logging

log = logging.getLogger(__name__)

def connect(params: dict):
    try:
        conn = ConnectHandler(**params)
        log.info(f"Connected to {params['host']}")
        return conn
    except NetmikoTimeoutException:
        log.error(f"Timeout: {params['host']} unreachable")
    except NetmikoAuthenticationException:
        log.error(f"Auth failed: {params['host']}")
    except Exception as e:
        log.error(f"Unexpected: {e}")
    return None

Multi-Device with ThreadPoolExecutor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from concurrent.futures import ThreadPoolExecutor, as_completed

INVENTORY = [
    {"device_type": "cisco_ios", "host": "10.0.0.1", "username": "admin", "password": "cisco"},
    {"device_type": "arista_eos","host": "10.0.0.2", "username": "admin", "password": "arista"},
]

def run_cmd(device, cmd):
    result = {"host": device["host"], "output": None, "error": None}
    try:
        with ConnectHandler(**device) as conn:
            result["output"] = conn.send_command(cmd)
    except Exception as e:
        result["error"] = str(e)
    return result

def run_all(inventory, cmd, workers=10):
    results = []
    with ThreadPoolExecutor(max_workers=workers) as ex:
        futures = {ex.submit(run_cmd, d, cmd): d["host"] for d in inventory}
        for f in as_completed(futures):
            results.append(f.result())
    return results

for r in run_all(INVENTORY, "show version"):
    status = "OK" if r["output"] else f"ERR: {r['error']}"
    print(f"{r['host']:15} {status}")

TextFSM Structured Output

1
2
3
4
5
6
7
8
with ConnectHandler(**device) as conn:
    interfaces = conn.send_command(
        "show ip interface brief",
        use_textfsm=True        # returns list of dicts
    )

for i in interfaces:
    print(f"{i['intf']:25} {i['ipaddr']:18} [{i['status'].upper()}]")

Key Takeaways

  • Always use with ConnectHandler(...) for automatic cleanup
  • Handle NetmikoTimeoutException and NetmikoAuthenticationException explicitly
  • Use ThreadPoolExecutor for multi-device — never loop serially in production
  • use_textfsm=True gives you structured output for free

Next up: Nornir — a framework that adds proper inventory management and task runners on top of Netmiko’s connection model.