Netmiko for Network Engineers¶
Netmiko is a Python library that drives the CLI of network devices over SSH. It handles prompts, paging, enable mode and config mode so you can write scripts instead of expect-style screen scrapers. This article takes you from a first connection to a concurrent, error-tolerant script that collects show output from many devices, parses it into data, and pushes configuration safely.
If you are still deciding between tools, read the NAPALM and Ansible articles too. Netmiko is the right starting point because it works on anything with a CLI, and the CLI is the one interface every device has.
Install and first connection¶
Use a virtual environment and Python 3.10 or newer.
Netmiko 4.x installs ntc-templates as a dependency, which is what the TextFSM parsing below uses.
from netmiko import ConnectHandler
device = {
"device_type": "cisco_xe",
"host": "192.0.2.10",
"username": "automation",
"password": "use-an-env-var-not-this",
"secret": "enable-secret", # only needed if you use enable mode
}
with ConnectHandler(**device) as conn:
conn.enable()
print(conn.find_prompt())
output = conn.send_command("show version")
print(output)
The with block disconnects cleanly when it exits. device_type selects the platform driver. Common values are cisco_ios, cisco_xe, cisco_nxos, cisco_xr, cisco_asa and arista_eos. For a platform not on that list, run python -c "from netmiko import ssh_dispatcher; print(sorted(ssh_dispatcher.CLASS_MAPPER_BASE))" to see what your installed version supports.
Warning
Do not hard-code credentials. Read them from environment variables, a secrets manager, or getpass. Anything in a script ends up in Git sooner or later.
Running show commands¶
send_command() sends one command and returns the output as a string. It waits until the prompt returns, so you do not need terminal length 0; Netmiko disables paging when it connects.
Two parameters matter in practice:
read_timeoutis how long Netmiko waits for output, in seconds. The default is 10. Slow commands such asshow tech-supportor a largeshow ip routeneed more. In Netmiko 4.x this replaces the olddelay_factortuning.expect_stringis a regex for the prompt or text that ends the command. You need it when the command ends on something other than the normal prompt, for example a confirmation question.
out = conn.send_command("show running-config", read_timeout=60)
out = conn.send_command_timing("copy running-config startup-config")
send_command_timing() ignores prompts and waits for output to go quiet. It is the fallback when prompt detection fails, but it is slower and less reliable than send_command().
Parsing output into data¶
Raw text is fine for a human and useless for logic. Netmiko can hand the output to a parser and return Python data structures instead.
TextFSM with ntc-templates¶
interfaces = conn.send_command("show ip interface brief", use_textfsm=True)
for intf in interfaces:
if intf["status"] == "up" and intf["proto"] == "down":
print(intf["interface"], "is up/down")
With use_textfsm=True you get a list of dictionaries, one per row. The field names come from the template in ntc-templates, so print one result to see the keys before you write code against them. If no template matches the command and platform, Netmiko returns the raw string unchanged, so check the type:
Command abbreviations matter. The template index matches the command text, and Netmiko handles common abbreviations, but typing the full command removes doubt.
Genie¶
Cisco's pyATS library ships Genie parsers that return nested dictionaries and cover many more commands per platform than ntc-templates.
bgp = conn.send_command("show ip bgp summary", use_genie=True)
neighbors = bgp["vrf"]["default"]["neighbor"]
Genie output is structured by the model Cisco chose, which is usually deeper than TextFSM output. Pick one parser per script and stay with it. TextFSM is lighter to install; Genie wins when you need a command ntc-templates does not cover.
Pushing configuration¶
send_config_set() enters config mode, sends each line, and exits. It takes a list of commands.
commands = [
"interface GigabitEthernet1/0/10",
"description uplink to dist-2",
"switchport mode trunk",
"switchport trunk allowed vlan 10,20,30",
]
output = conn.send_config_set(commands)
print(output)
conn.save_config()
save_config() runs write memory (or the platform equivalent). It is not automatic, so a reload will lose anything you forgot to save.
To push a file, use send_config_from_file("changes.cfg").
send_config_set() does not tell you whether the device accepted each line. The output contains the device's error text, such as % Invalid input detected, and Netmiko will not raise an exception for it. Check the output yourself:
ERRORS = ("% Invalid", "% Incomplete", "% Ambiguous", "% Access denied")
def push(conn, commands):
output = conn.send_config_set(commands)
bad = [line for line in output.splitlines() if line.startswith(ERRORS)]
if bad:
raise RuntimeError("Config errors: " + "; ".join(bad))
return output
Netmiko 4.x also offers error_pattern on send_config_set(), which raises ConfigInvalidException when a line of output matches. Either approach works; the point is to not trust silence.
Tip
Capture show running-config before and after a change and save both. A diff of the two is your change record, and the "before" copy is your manual rollback.
Handling errors¶
Connections fail in a few well-known ways, and Netmiko has an exception for each. Import them from netmiko:
from netmiko import (
ConnectHandler,
NetmikoTimeoutException,
NetmikoAuthenticationException,
)
from paramiko.ssh_exception import SSHException
def collect(device):
try:
with ConnectHandler(**device) as conn:
return device["host"], conn.send_command("show version", use_textfsm=True)
except NetmikoTimeoutException:
return device["host"], "ERROR: timeout (unreachable or SSH not responding)"
except NetmikoAuthenticationException:
return device["host"], "ERROR: authentication failed"
except SSHException as exc:
return device["host"], f"ERROR: SSH problem: {exc}"
Catch the specific cases so your report says why a device failed. Authentication failures deserve special care at scale: a bad password pushed to 200 devices can lock the automation account everywhere at once. Stop the run when you see the first authentication failure instead of letting every thread retry.
Many devices at once¶
Doing 200 devices one at a time is slow because most of the time is spent waiting on the network. Threads fit this job well.
from concurrent.futures import ThreadPoolExecutor, as_completed
import os, yaml
with open("inventory.yaml") as fh:
hosts = yaml.safe_load(fh)["hosts"]
def build(host):
return {
"device_type": "cisco_xe",
"host": host,
"username": os.environ["NET_USER"],
"password": os.environ["NET_PASS"],
"conn_timeout": 20,
}
results = {}
with ThreadPoolExecutor(max_workers=20) as pool:
futures = [pool.submit(collect, build(h)) for h in hosts]
for fut in as_completed(futures):
host, data = fut.result()
results[host] = data
failed = {h: d for h, d in results.items() if isinstance(d, str)}
print(f"{len(results) - len(failed)} ok, {len(failed)} failed")
for h, msg in failed.items():
print(h, msg)
graph LR
INV["inventory.yaml"] --> POOL["ThreadPoolExecutor<br/>20 workers"]
POOL --> D1["Device 1"]
POOL --> D2["Device 2"]
POOL --> D3["Device N"]
D1 --> RES["results dict"]
D2 --> RES
D3 --> RES
RES --> REP["Report and failures"]
Pick max_workers deliberately. Each worker is an SSH session that uses CPU on the device's control plane, and AAA servers have limits. Start around 10 to 20, watch the TACACS+ or RADIUS server, and raise it if both sides are comfortable. For configuration changes, roll out in batches (a pilot device, then a small group, then the rest) and stop if the failure count passes a threshold.
Each thread builds its own connection. Never share one ConnectHandler object between threads.
Practical habits¶
- Keep the device list in a file (YAML or CSV), not in code. Better, generate it from your source of truth.
- Log with the
loggingmodule, and setsession_log="netmiko.log"in the device dictionary when debugging a stubborn prompt problem. Session logs contain everything sent, including secrets typed at prompts, so do not leave them enabled in production. - Set
fast_cli=Trueonly after your script works. It trims internal delays, and on slow devices it can cut off output. - Use
conn.send_command("show clock")as a cheap sanity test when you suspect a session is hung. - Prefer
send_command()for reads andsend_config_set()for writes. Do not send config lines throughsend_command(). - On devices that need
enable, callconn.enable()after connecting and supplysecretin the device dictionary.
Tip
Netmiko is a good fit for one-off audits, ad-hoc changes and getting data out of platforms that have no API. When you need diffs, atomic commits and rollback across vendors, look at NAPALM. When you need inventory, group variables and a repeatable change process for a team, look at Ansible.
References¶
- Netmiko on GitHub
- ntc-templates on GitHub
- Paramiko documentation
- pyATS and Genie documentation
- Python concurrent.futures
- CCNP course home for the routing and switching background these scripts operate on
Unlock the full question banks and cram sheets
A free account opens every question bank, the timed practice exams, the printable cram sheets, all the simulations and the lab files. Without one you see a 5-question sample of each.
No card needed. Study tips by email are optional and off unless you tick the box. Your address is never sold or shared. Privacy policy ยท Terms
Signed in as . Your account
You're on the free account, so lessons after the first module, question banks, exams and cram sheets show a sample. See Track Pass and All-Access
Also from NetManager Systems: NetManager, network monitoring for multi-vendor networks. Try the NetManager, NetDiag, PCAP Analyzer and OpsDash demos: every new account gets a free 7-day read-only login to each.