If you have ever pasted the same VLAN config into 40 switches at midnight, you already know why Netmiko exists. SSH into one device, run a few commands, hit enter, repeat. That workflow falls apart the moment one device fails halfway through, or a teammate fat-fingers a port number, or leadership asks which switches are running an outdated image.
Netmiko is a Python library that turns those repetitive SSH sessions into scripts you can run, version, and rerun on demand. For engineers who already know the CLI but have not written much Python, it is usually the first library on the list. This guide walks through how to install it, connect to a Cisco device, pull operational data, push configuration, and compare it to the libraries you will eventually run into next: Paramiko, NAPALM, and Nornir.
Before you write a single line of code, here is where Netmiko fits relative to the other tools you will see when you start automating networks.
| Tool | Purpose | Learning Curve | Best For |
| Netmiko | SSH-based, multi-vendor CLI automation in Python | Low | Engineers who know the CLI and want scripted CLI |
| Paramiko | Raw SSH library (Netmiko sits on top of it) | Medium | Custom SSH workflows, non-network devices |
| NAPALM | Vendor-agnostic API for network device state | Medium | Configuration management, network validation |
| Nornir | Pure-Python automation framework | High | Inventory-driven automation at scale |
| Ansible | Declarative, playbook-based automation | Medium | Cross-team automation, no-Python teams |
Netmiko is the right starting point if you can already configure a Cisco router by hand and you want a programmatic way to do the same thing across 50 of them. It is intentionally close to the CLI. You send commands as strings, you read output as strings, and the library handles the SSH session and prompt detection for you.
If you run a team of network engineers who all need a safe place to test Python scripts before touching production, CloudMyLab's hosted lab platform gives every engineer a managed Cisco, Juniper, Arista, or FortiNet topology in the browser. Team plans land in the $2K–$5K initial / $45K 3-year TCO range, well below the $50K–$100K cost of building physical lab hardware for the same coverage.
Netmiko is an open-source Python library that simplifies SSH connections to network devices. It was created by Kirk Byers and is built on top of Paramiko, the lower-level SSH library for Python. Where Paramiko gives you a raw SSH channel and leaves prompt handling and paging to you, Netmiko handles all of that automatically for network gear.
The practical difference is what your code looks like. With Paramiko you spend half your script reading bytes off a socket and stripping ANSI escape codes. With Netmiko you write net_connect.send_command("show ip interface brief") and you get the output back as a clean string.
Vendor coverage out of the box is wide. Cisco IOS, IOS-XE, NX-OS, ASA, Juniper Junos, Arista EOS, HP ProCurve, Aruba, Palo Alto, FortiNet, MikroTik, and many others. The library knows how to handle each vendor's quirks. Cisco prompts change between user and privileged mode. Juniper has its own configuration mode. MikroTik needs color output disabled before anything works cleanly. You pick the right device_type string and Netmiko sorts the rest.
For most network engineers moving into automation, Netmiko is the bridge between "I know what show running-config looks like" and "I have a Python script that runs show running-config against 200 devices and checks for misconfigurations." That bridge is why it shows up first when someone asks how to start with Python network automation.
Netmiko installs through pip. You should run it inside a virtual environment so it does not collide with system Python or other automation projects:
python -m venv netmiko-env
source netmiko-env/bin/activate # Linux/macOS
netmiko-env\Scripts\activate # Windows
pip install netmiko
Once it is installed, the smallest working Netmiko script is a device dictionary plus a ConnectHandler call. Here is what a minimal Cisco IOS connection looks like:
from netmiko import ConnectHandler
device = {
"device_type": "cisco_ios",
"host": "192.168.1.1",
"username": "admin",
"password": "your_password",
"secret": "your_enable_password",
"port": 22,
}
net_connect = ConnectHandler(**device)
net_connect.enable()
print(net_connect.find_prompt())
net_connect.disconnect()
A few things worth knowing the first time you run this.
The device_type field is the most important value in the dictionary. Get it wrong and Netmiko picks the wrong prompt-handling logic, then hands you garbage output or a hang. For Cisco IOS routers and switches, use cisco_ios. For NX-OS, cisco_nxos. Juniper devices want juniper_junos. The full list lives in the Netmiko source under ssh_dispatcher.py.
The secret field is the privileged exec password. Calling net_connect.enable() switches the session into enable mode using that password. If your device does not require enable mode (most modern Cisco gear with role-based access does not), you can omit secret and skip the .enable() call entirely.
Hardcoding credentials in a script is a habit to break early. Pull them from environment variables or a secrets manager. A common starting pattern is os.environ.get("NETWORK_USER") and os.environ.get("NETWORK_PASS"), which keeps credentials out of the script and out of version control.
The most common first task with Netmiko is pulling the same operational command from a list of devices. Inventory checks, interface audits, finding out which switches are still running an IOS train you thought you upgraded six months ago. The pattern looks like this:
from netmiko import ConnectHandler
devices = [
{"device_type": "cisco_ios", "host": "192.168.1.1",
"username": "admin", "password": "your_password"},
{"device_type": "cisco_ios", "host": "192.168.1.2",
"username": "admin", "password": "your_password"},
{"device_type": "cisco_ios", "host": "192.168.1.3",
"username": "admin", "password": "your_password"},
]
for device in devices:
with ConnectHandler(**device) as net_connect:
version = net_connect.send_command("show version")
interfaces = net_connect.send_command("show ip interface brief")
print(f"=== {device['host']} ===")
print(version.split("\n")[0])
print(interfaces)
print()
The with block uses Netmiko's context manager support, which makes sure the SSH session closes cleanly even if your script crashes mid-loop. That matters more than it sounds. SSH sessions left hanging on a Cisco device can hold a vty line until the timeout expires, and a script that opens 50 connections without closing them will lock out other admins (sometimes including you).
Netmiko also supports parsed output through TextFSM and Genie. If you pass use_textfsm=True, the library returns a list of dictionaries instead of a raw string:
output = net_connect.send_command("show ip interface brief", use_textfsm=True)
for interface in output:
if interface["status"] == "up":
print(f"{interface['intf']} - {interface['ipaddr']}")
Parsed output is the difference between writing a one-off script and writing something you can hand to your manager as a report. Once you start using TextFSM regularly, you stop scraping show commands with regex and start treating Netmiko output as structured data.
Reading data is safe. Pushing configuration is where engineers get cautious, and rightly so. Netmiko gives you send_config_set() for sending lists of configuration commands. The library enters config mode, sends each line, and exits config mode automatically:
from netmiko import ConnectHandler
device = {
"device_type": "cisco_ios",
"host": "192.168.1.1",
"username": "admin",
"password": "your_password",
}
vlan_commands = [
"vlan 30",
"name Engineering",
"vlan 40",
"name Sales",
"exit",
]
interface_commands = [
"interface GigabitEthernet0/1",
"switchport mode access",
"switchport access vlan 30",
"description Eng workstation",
"no shutdown",
]
with ConnectHandler(**device) as net_connect:
net_connect.enable()
output = net_connect.send_config_set(vlan_commands)
output += net_connect.send_config_set(interface_commands)
net_connect.save_config()
print(output)
A few details that bite people the first time.
send_config_set() does not save your config. It applies the commands to running-config and stops there. If the switch reboots before you call save_config() or send write memory, the change is gone. Always pair config pushes with an explicit save unless you have a specific reason not to.
The library returns the full session log of what it sent and what the device responded. Capture that output and write it to a file. When something goes sideways at 2am, the session log is the only record of what your script actually did.
For configurations that live in a file (which is most real-world automation), pass the filename instead of an in-memory list:
net_connect.send_config_from_file("vlan_changes.txt")
This is the pattern most teams settle on. Configs sit in a Git repo, your Netmiko script reads the file, applies it, and logs the result. The change is reviewable, repeatable, and rollback-friendly. For a deeper look at what goes wrong when this discipline breaks down, why network changes keep breaking production is worth a read.
Once you have written a few Netmiko scripts, you start seeing the other libraries in tutorials and Stack Overflow answers. Paramiko, NAPALM, and Nornir each solve a different problem. Picking the wrong one early adds a lot of unnecessary work later.
Paramiko is the SSH library Netmiko sits on top of. It implements SSH at the protocol level: socket connection, key exchange, authentication, channel multiplexing. It does not know what a Cisco prompt looks like. It does not handle paging. It does not parse output.
You should use Paramiko directly if you are connecting to something that is not a network device, like a Linux server, a custom appliance, or a vendor Netmiko does not support. For anything that runs a network OS, Netmiko is faster to write and easier to debug.
| Netmiko | Paramiko | |
| Layer | High-level network device automation | Low-level SSH protocol |
| Prompt handling | Automatic (per vendor) | Manual |
| Paging | Handled (terminal length 0 sent automatically) | Manual |
| Multi-vendor support | Yes (90+ device types) | Vendor-agnostic but you write the logic |
| Best for | CLI-driven network automation | Custom SSH or non-network targets |
NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) takes a different approach. Instead of sending raw CLI commands, NAPALM exposes a vendor-agnostic API. You call device.get_facts() and you get back a structured dict no matter whether the underlying device is Cisco, Juniper, or Arista.
| Netmiko | NAPALM | |
| Interaction style | Raw CLI commands | Vendor-agnostic API methods |
| Output | Strings (or TextFSM-parsed) | Structured dicts |
| Configuration model | Send config lines | Replace, merge, commit, rollback |
| Config diff before apply | No | Yes |
| Best for | Quick scripts, ad-hoc automation | Configuration management, validation |
NAPALM wins for configuration management because of its merge/replace/commit model. You generate a candidate config, ask the device for a diff before applying, commit the change, and roll back if something fails. Netmiko does not have any of that built in. For quick operational scripts, plain Netmiko is enough. For a config management pipeline, you probably want NAPALM, often with Netmiko underneath for the corners NAPALM does not cover.
Nornir is a pure-Python automation framework. It is not a replacement for Netmiko. It is a layer above it. Nornir handles inventory, parallel execution, task chaining, and reporting. You plug Netmiko (or NAPALM, or both) into Nornir as the actual transport.
| Netmiko alone | Nornir + Netmiko | |
| Inventory | DIY (lists, YAML, CSV) | Built-in (hosts, groups, defaults) |
| Parallelism | DIY (threading) | Built-in |
| Tasks | One script per workflow | Composable task functions |
| Best for | <50 devices, simple workflows | Inventory-driven automation at scale |
If you are managing a small lab or fewer than 50 production devices, plain Netmiko with a for loop is fine. Once you hit a few hundred devices, or you want to run the same task across different inventory groups, Nornir makes everything cleaner. The same script that took 40 minutes serially with Netmiko alone often finishes in under three when Nornir parallelizes it.
Ansible is the other tool you will see referenced constantly. It is not a Python library you import. It is a separate automation framework with its own language (YAML playbooks) and its own execution model. Some Ansible network modules use Netmiko under the hood, but you do not write Python to use Ansible.
If your team includes engineers who do not write Python, Ansible is usually the better choice because the playbooks read like configuration. If your team writes Python and wants programmatic control, Netmiko (or Nornir on top of it) gives you more flexibility. Many shops run both: Ansible for cross-team, broad-strokes automation, and Netmiko or Nornir for targeted Python work. For a deeper look at how Ansible fits into network automation, see Ansible CLI vs Ansible Automation Platform and the step-by-step Ansible setup guide.
The biggest reason engineers stick with Netmiko after their first script is the vendor coverage. Almost every networking vendor that uses an SSH-based CLI is supported. The full mapping lives in ssh_dispatcher.py in the Netmiko source, but here are the device types you will probably reach for first:
| Vendor | device_type strings |
| Cisco IOS / IOS-XE | cisco_ios, cisco_xe |
| Cisco NX-OS | cisco_nxos |
| Cisco ASA | cisco_asa |
| Cisco IOS-XR | cisco_xr |
| Juniper Junos | juniper_junos |
| Arista EOS | arista_eos |
| Aruba (older) | aruba_os, aruba_osswitch |
| HP ProCurve | hp_procurve |
| HP Comware | hp_comware |
| Palo Alto | paloalto_panos |
| FortiNet | fortinet |
| MikroTik RouterOS | mikrotik_routeros |
| Linux (generic SSH) | linux |
Multi-vendor automation in one script looks like this:
from netmiko import ConnectHandler
inventory = [
{"device_type": "cisco_ios", "host": "10.1.1.1", "username": "admin", "password": "pw"},
{"device_type": "juniper_junos", "host": "10.1.1.2", "username": "admin", "password": "pw"},
{"device_type": "arista_eos", "host": "10.1.1.3", "username": "admin", "password": "pw"},
]
vendor_commands = {
"cisco_ios": "show version",
"juniper_junos": "show version",
"arista_eos": "show version",
}
for device in inventory:
with ConnectHandler(**device) as conn:
cmd = vendor_commands[device["device_type"]]
output = conn.send_command(cmd)
print(f"--- {device['host']} ({device['device_type']}) ---")
print(output[:200])
The same script works against three vendors because Netmiko abstracts the SSH layer. The output formats differ wildly (Junos returns very different text than Cisco IOS), but the Python code that gets you there does not change. This is why CloudMyLab's hosted labs default to multi-vendor topologies. Testing automation against four different vendors in one lab session is the only way to catch the edge cases before they hit your production network.
A working Netmiko script is not the same as a production-ready one. The difference is what happens when things go wrong, because in network automation things go wrong often. A device is unreachable. An SSH key changed last Tuesday and nobody told you. A credential rotation killed half your inventory.
The exceptions worth knowing:
from netmiko import ConnectHandler
from netmiko.exceptions import (
NetmikoTimeoutException,
NetmikoAuthenticationException,
)
device = {
"device_type": "cisco_ios",
"host": "192.168.1.1",
"username": "admin",
"password": "your_password",
}
try:
with ConnectHandler(**device) as net_connect:
output = net_connect.send_command("show version")
print(output)
except NetmikoTimeoutException:
print(f"Could not reach {device['host']} - device unreachable or SSH not responding")
except NetmikoAuthenticationException:
print(f"Authentication failed on {device['host']} - check credentials")
except Exception as e:
print(f"Unexpected error on {device['host']}: {e}")
Always catch NetmikoTimeoutException and NetmikoAuthenticationException separately. They mean different things, and you usually want different responses (retry on a timeout, fail loudly on auth).
For logging, Netmiko writes session logs to a file if you pass session_log to ConnectHandler. This captures every byte sent and received, which is gold during debugging:
device["session_log"] = f"logs/{device['host']}.log"
For parallelism, Netmiko is thread-safe per connection. Wrap your loop in concurrent.futures.ThreadPoolExecutor to run against many devices in parallel:
from concurrent.futures import ThreadPoolExecutor
def collect_version(device):
with ConnectHandler(**device) as conn:
return device["host"], conn.send_command("show version")
with ThreadPoolExecutor(max_workers=10) as pool:
results = list(pool.map(collect_version, inventory))
Ten parallel threads usually finish in seconds what a serial loop would take minutes to complete on a typical campus network. Do not crank max_workers too high. SSH on most network gear caps at 10–15 concurrent vty lines, and you will lock out admins (yourself included) if you push past that.
For more on the broader operational discipline around automation, common network automation challenges and the mistakes engineers make covers what tends to break the moment scripts move past the lab.
The single biggest mistake new automation engineers make is testing a script against production. A typo in send_config_set() can take down 40 switches in 30 seconds, and the only thing standing between you and that career-ending email is a lab.
There are a few realistic ways to give yourself somewhere safe to test.
Free local emulators. GNS3 and EVE-NG Community both run real Cisco IOS images on your laptop. They are free, but you have to source the images legally and you need 16+ GB of RAM for a useful topology. For a side-by-side breakdown, see EVE-NG vs GNS3.
Self-hosted lab on a server. If you already have a homelab, dropping EVE-NG or CML on a beefy server gives you a permanent test environment. The downside is that you maintain it. Image management, license tracking, GNS3 VM tuning, and OS patching all become your problem.
Hosted lab platforms. A managed service runs the emulator on cloud hardware sized for serious topologies, with images already licensed and pre-loaded. CloudMyLab's Lab as a Service and hosted EVE-NG sit in this tier. You log in through a browser, build a topology with Cisco, Juniper, Arista, and FortiNet in the same lab, and run your Netmiko scripts against it without ever touching your production network.
| Option | Upfront Cost | Multi-vendor | Maintenance | Best For |
| GNS3 / EVE-NG Community (local) | $0–$200 (RAM) | Limited | High (you do it) | Solo learners |
| Self-hosted CML / EVE-NG Pro | $1K–$3K hardware + license | Yes | High (you do it) | Dedicated home lab users |
| Hosted lab platform | $300–$900/yr (individual), $2K–$5K (team) | Yes (pre-loaded) | None | Teams, time-constrained engineers |
If you are running automation training for a team, hosted labs win on uniformity. Every engineer gets the same topology, the same images, the same starting state. There is no "works on my laptop" problem because nobody has the lab on their laptop. For more on the case for running automation work in a managed environment, see Network Engineer Home Cloud Lab and Network Automation POCs and Learning at CloudMyLab.
Netmiko is a starting point, not a destination. Most engineers who use it long enough end up adding NAPALM, Nornir, Ansible, or all three to their toolbox. The right path depends on where you are now and what you are trying to do.
| If you are... | Start with |
| New to Python, comfortable with the CLI | Netmiko alone, simple for loops |
| Running ad-hoc scripts against under 50 devices | Netmiko + ThreadPoolExecutor |
| Building a config management pipeline | NAPALM + Netmiko underneath |
| Managing 100+ devices with inventory groups | Nornir + Netmiko |
| Working across teams, including non-Python folks | Ansible (with Netmiko-backed modules) |
| Validating multi-vendor automation before rollout | Hosted lab with Netmiko scripts |
The most expensive mistake is not picking the wrong tool. It is testing against production because there was no lab. A $25–$45 monthly hosted lab subscription pays for itself the first time it catches a send_config_set() typo before it reaches a live switch.
If you want a place to run Netmiko against real Cisco, Juniper, and Arista images without a 40-hour setup detour, start with a CloudMyLab hosted lab. For the broader case for treating automation as a career skill rather than a side project, see Network Automation: Critical to IT and why automation pipelines fail without proper test environments.
Netmiko is a Python library used for SSH-based automation of network devices. Engineers use it to log into routers, switches, and firewalls programmatically, run show commands to gather operational data, and push configuration changes across many devices at once. It supports Cisco, Juniper, Arista, HP, Aruba, Palo Alto, FortiNet, MikroTik, and 80+ other vendors.
Netmiko is built on top of Paramiko and is better suited for network device automation. Paramiko is a low-level SSH library that handles the SSH protocol but leaves prompt detection, paging, and command execution to you. Netmiko adds vendor-aware logic for those tasks. Use Netmiko for network gear and Paramiko directly only when you need raw SSH for non-network targets.
Netmiko sends raw CLI commands and returns the output as strings. NAPALM provides a vendor-agnostic API that returns structured data and supports config merge, replace, commit, and rollback. Netmiko is faster to write quick scripts in. NAPALM is better for configuration management pipelines where you need diff-before-apply and rollback semantics.
Netmiko and Ansible solve overlapping but different problems. Netmiko is a Python library you import to write imperative scripts. Ansible is a separate framework that uses YAML playbooks and a different execution model. If your team writes Python, Netmiko gives you more programmatic control. If your team includes non-Python engineers, Ansible is usually easier to share. Many shops run both.
Open PowerShell or Command Prompt, create a virtual environment with python -m venv netmiko-env, activate it with netmiko-env\Scripts\activate, then run pip install netmiko. You also need OpenSSH client support, which is built into Windows 10 and 11 by default. Avoid installing Netmiko into system Python if you have multiple automation projects.
The device_type field in the Netmiko device dictionary tells the library which vendor's prompt-handling logic to use. Common values are cisco_ios, cisco_nxos, cisco_asa, juniper_junos, arista_eos, paloalto_panos, and mikrotik_routeros. Pick the wrong one and Netmiko misreads the prompt, hangs, or returns empty output. The full list lives in ssh_dispatcher.py in the Netmiko source.
Yes. Netmiko is usually the recommended first library for engineers moving from CLI work into Python automation. The mental model is close to the CLI you already know. You connect, you send a string command, you get a string back. You do not need to learn SSH internals, async Python, or YAML to be productive in your first hour.
Wrap your Netmiko loop in concurrent.futures.ThreadPoolExecutor with max_workers set between 5 and 10. Netmiko is thread-safe per connection, so each thread handles one device. Avoid pushing past 10–15 concurrent connections against the same network because most network gear caps SSH vty lines around that range. For inventory-driven parallelism at scale, switch to Nornir, which handles parallel execution across hundreds of devices with built-in concurrency controls.