Netmiko guide

Netmiko, explained: what it is, how to use it, and when to stop building

Netmiko is an open source, multi-vendor Python library, maintained by Kirk Byers, that simplifies CLI connections to network devices over SSH, Telnet and serial, built on top of Paramiko.

Netmiko at a glance

GitHub describes the project as “Multi-vendor library to simplify Paramiko SSH connections to network devices”. It is a focused library for network device CLI automation, used for one-off tasks, custom workflows and ongoing production automation.

Latest release
4.8.0, released 21 September 2026 (release page on PyPI)
Licence
MIT
Python
3.10 or newer (below 4.0)
Maintainer
Kirk Byers (ktbyers)
Platforms
At the time of writing: 8 regularly tested, 61 with limited testing and 68 experimental, counted in PLATFORMS.md at release v4.8.0

Verified on against PyPI, the GitHub repository and PLATFORMS.md at tag v4.8.0. The code examples were reviewed against the 4.8.0 source. None was run against a network device.

What is Netmiko?

Much network automation that works by screen-scraping comes down to two jobs: gathering show command output and making configuration changes. Raw SSH leaves a lot of small details to you. Your code has to recognise the prompt, switch off paging, enter enable and configuration modes, cope with each vendor's quirks and judge how long to wait for output. Netmiko takes care of those parts, so a script can ask for a command and get its output back.

Netmiko supports ongoing production automation as well as one-off tasks and custom workflows. Many engineers use it for quick jobs, and teams also build long-running automation on it, often with a framework such as Nornir around it. It is a tool to choose on its merits, not a stage that teams are expected to leave behind.

Netmiko is maintained by Kirk Byers, the founder of Twin Bridges Technology, a business specialising in network automation training, together with a large community of contributors who add and test platforms. The official project, its README and documentation and its examples directory are the places to go for the authoritative detail.

Much of what makes Netmiko valuable comes from that community. Every platform that works today works because engineers wrote, tested and fixed a driver for it.

How Netmiko relates to Paramiko

Paramiko is a pure-Python implementation of the SSHv2 protocol, with client and server functionality. Netmiko uses Paramiko as its SSH transport and adds network device behaviour on top: knowing what a prompt looks like, how a platform enters configuration mode, and how to tell when a command has finished.

Netmiko sits between Paramiko and your codeA stack of three layers. At the top, your script or a framework such as Nornir. In the middle, Netmiko, which adds network device behaviour. At the bottom, Paramiko, the SSHv2 transport.Your script, or a framework such as NornirDecides what to run, and on which devicesNetmikoPrompts, paging, config modes, per-platform driversParamikoSSHv2 transport
Your code calls Netmiko, and Netmiko uses Paramiko for the SSH connection.

Your own script, or a framework such as Nornir, sits on top of Netmiko. Netmiko also supports Telnet and serial connections through its _telnet and _serial device types, which the device types section covers.

Install Netmiko and make a first connection

Netmiko 4.8.0 needs Python 3.10 or newer (below 4.0). Install it into a virtual environment with pip.

Examples written for Netmiko 4.8.0 and checked against its source. Run them against a lab device before production. None of them was run against a network device while writing this guide. The parsing examples were also run offline against sample output.

Install into a virtual environment
python3 -m venv .venv
source .venv/bin/activate
pip install netmiko

Take credentials from environment variables instead of writing them into the script, and use documentation addresses (192.0.2.0/24) while you experiment.

Set credentials in the environment, never in the script
# Set these in your shell, or load them from a secret store.
# Never commit them to a repository.
export NET_USER="netops"
export NET_PASS="replace-with-your-password"

# Only if the device needs an enable secret:
export NET_ENABLE="replace-with-the-enable-secret"

A connection starts from a device dictionary. The device_type picks the driver, and ConnectHandler returns a connection object.

First connection with ConnectHandler
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

conn = ConnectHandler(**device)
print(conn.find_prompt())
conn.disconnect()

The context manager form does the same job and closes the session for you, even if the code inside raises an error.

The context manager form closes the session for you
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

with ConnectHandler(**device) as conn:
    print(conn.find_prompt())

Privileged mode

Some devices log you in at a user level. If yours needs an enable password, add it to the device dictionary as secret and call enable() when the session is not already in privileged mode. Take the secret from the environment, like the other credentials.

Enter privileged mode with a secret when the device needs it
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
    "secret": os.environ["NET_ENABLE"],
}

with ConnectHandler(**device) as conn:
    # Enter privileged mode only if the session is not already in it.
    if not conn.check_enable_mode():
        conn.enable()
    print(conn.find_prompt())

Production note: SSH host keys

By default Netmiko accepts SSH host keys it has not seen before (ssh_strict is False). That is convenient in a lab, and it means the connection does not check that the device is the one you meant. For production, set ssh_strict=True and load trusted keys with system_host_keys=True (your known_hosts file), or with alt_host_keys=True and an alt_key_file. The trusted keys must already be provisioned from a source you trust. A host whose key is not in the file is refused.

Verify SSH host keys against a known_hosts file
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
    # Refuse hosts whose key is not already trusted.
    "ssh_strict": True,
    # Load trusted keys from the user's known_hosts file.
    "system_host_keys": True,
}

with ConnectHandler(**device) as conn:
    print(conn.find_prompt())

Core Netmiko methods

Most scripts use a handful of methods. Each one below has a short explanation and an example.

send_command

send_command runs one command and returns its output once the prompt comes back. read_timeout sets how many seconds to wait for the output (the default is 10), so raise it for slow commands.

send_command, with a longer read_timeout for a slow command
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

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

    # A slow command: allow up to 120 seconds for the output.
    config = conn.send_command("show running-config", read_timeout=120)
    print(len(config.splitlines()), "lines")

expect_string is a regular expression that tells Netmiko where the output ends. By default Netmiko builds that pattern from the device's prompt, so you rarely need it. Use it when the output ends with something other than the prompt, such as a question the device asks, and make the pattern as specific as the expected text. A pattern that is too broad can end the read early, and one that never matches ends in a ReadTimeout. This guide does not include an expect_string example, because the right pattern depends on the device and the command, so try it against a lab device first.

send_command_timing

send_command_timing reads on a timer instead of waiting for a prompt. It suits commands that ask a question, such as a confirmation, or that do not return to a normal prompt. Read the returned text before you answer, as the wording differs between platforms.

send_command_timing for commands that ask a question
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

with ConnectHandler(**device) as conn:
    # Timing based reads are useful when the device asks for confirmation.
    output = conn.send_command_timing("clear counters")
    if "confirm" in output:
        output += conn.send_command_timing("\n")
    print(output)

send_config_set and send_config_from_file

send_config_set takes a list of configuration commands, enters configuration mode, sends each line and leaves configuration mode again. send_config_from_file does the same with the commands read from a text file.

Sending a command does not guarantee that the device accepted it. A device can reject one line and carry on with the rest. Netmiko's command echo check confirms that a command was typed, which is not the same as the device applying it. Inspect the returned output, or pass error_pattern, a regular expression matched against the device's replies, so that Netmiko raises ConfigInvalidException. The right pattern is specific to the platform. Privileged mode may also be needed first (see the privileged mode note above).

send_config_set, checking the device's replies for errors
import os

from netmiko import ConnectHandler
from netmiko.exceptions import ConfigInvalidException

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

commands = [
    "interface Loopback100",
    "description set by netmiko example",
]

with ConnectHandler(**device) as conn:
    try:
        # error_pattern is a regular expression matched against the device's
        # replies. Cisco IOS error lines start with "% ", so this pattern is
        # specific to that kind of platform.
        output = conn.send_config_set(commands, error_pattern=r"^% ")
    except ConfigInvalidException as err:
        print(f"The device rejected a command: {err}")
        raise
    print(output)

The file version needs the text file to exist where the script runs. Each line is one command.

send_config_from_file reads the commands from a text file
import os

from netmiko import ConnectHandler
from netmiko.exceptions import ConfigInvalidException

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

with ConnectHandler(**device) as conn:
    try:
        # loopback100.txt must exist in the working directory.
        output = conn.send_config_from_file(
            "loopback100.txt", error_pattern=r"^% "
        )
    except ConfigInvalidException as err:
        print(f"The device rejected a command: {err}")
        raise
    print(output)
loopback100.txt, one command per line
interface Loopback100
 description set by netmiko example

save_config

save_config depends on the driver. On Cisco IOS it saves the running configuration to the startup configuration (the driver sends write mem by default). Other drivers send their own command, and some do not implement it at all: the IOS-XR driver raises NotImplementedError and expects you to use commit() instead.

save_config on Cisco IOS saves the running configuration to startup
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

with ConnectHandler(**device) as conn:
    conn.send_config_set(["interface Loopback100", "no shutdown"])
    print(conn.save_config())

commit

Some platforms, such as Junos and Cisco IOS-XR, hold changes in a candidate configuration until you commit them. Pass exit_config_mode=False to send_config_set, call commit(), then call exit_config_mode(). The IOS-XR driver's commit() enters configuration mode if needed and raises ValueError when the device reports a failed commit. It also accepts confirm, comment and replace for the commit variants IOS-XR offers, which this guide does not demonstrate. Junos's commit() has its own options, so check its signature in the Netmiko source before you use it.

commit on a commit-based platform (Cisco IOS-XR shown)
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_xr",
    "host": "192.0.2.30",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

with ConnectHandler(**device) as conn:
    # Stay in config mode so there is something to commit.
    conn.send_config_set(
        ["interface Loopback100", "description set by netmiko example"],
        exit_config_mode=False,
    )
    try:
        print(conn.commit())
    except ValueError as err:
        # The IOS-XR driver raises ValueError when the device reports a failed commit.
        print(f"Commit failed: {err}")
        raise
    conn.exit_config_mode()

file_transfer

file_transfer copies a file to or from a device over SCP and returns a dictionary that says whether the file existed, was transferred and was verified. Use direction="get" to pull a file instead.

It does not work on every supported platform. File transfer is implemented for a subset of drivers, including Cisco IOS, IOS-XE, NX-OS and IOS-XR, Arista EOS, Juniper Junos and Linux, and PLATFORMS.md lists the supported Secure Copy device types. The device also has to have SCP enabled and permit your account to use it, with enough free space at the destination.

file_transfer copies a file to the device over SCP
import os

from netmiko import ConnectHandler, file_transfer

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

with ConnectHandler(**device) as conn:
    result = file_transfer(
        conn,
        source_file="notes.txt",
        dest_file="notes.txt",
        file_system="flash:",
        direction="put",
        overwrite_file=False,
    )
    print(result["file_exists"], result["file_transferred"], result["file_verified"])

Netmiko device types and supported platforms

The device_type string selects the driver class that handles prompts, paging and configuration mode for that platform. Common values include cisco_ios, cisco_xe, cisco_nxos, cisco_xr, juniper_junos and arista_eos.

Telnet and serial variants

Telnet and serial support is not a suffix you can add to any device type. A variant exists only if it is mapped in Netmiko itself. Mapped in Netmiko 4.8.0 are 62 Telnet device types, such as cisco_ios_telnet and arista_eos_telnet, and two serial ones, cisco_ios_serial and furukawa_fitelnet_serial. A device type that is not mapped is not supported, so check the release you use.

Autodetection

If you do not know the platform, SSHDetect can guess it. It connects, tries a few commands and scores the likely device types. autodetect() returns the best match, or None when nothing matches, and potential_matches holds the scores. It works from its own detection map, about 50 device types in 4.8.0, which is smaller than the full list of supported platforms. Handle the None case before you pass the result to ConnectHandler.

SSHDetect guesses the device_type, and can return None
import os

from netmiko import ConnectHandler, SSHDetect

remote = {
    "device_type": "autodetect",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

guesser = SSHDetect(**remote)
best_match = guesser.autodetect()
print(best_match)
print(guesser.potential_matches)

if best_match is None:
    raise SystemExit("No match found. Set device_type yourself.")

remote["device_type"] = best_match
with ConnectHandler(**remote) as conn:
    print(conn.find_prompt())

Testing tiers

PLATFORMS.md groups platforms into three tiers. At the time of writing, the file at release v4.8.0 lists 8 regularly tested platforms, 61 with limited testing and 68 experimental. Those counts were reproduced by script against that tag. The regularly tested platforms are:

  • Arista vEOS
  • Cisco IOS
  • Cisco IOS-XE
  • Cisco IOS-XR
  • Cisco NX-OS
  • Cisco SG300
  • Juniper Junos
  • Linux

The counts change as the project grows. The live PLATFORMS.md on GitHub follows the development branch, so it can differ from the release.

Parsing output: TextFSM, TTP and Genie

send_command returns text by default. Three options turn that text into structured data. If parsing fails, Netmiko returns the raw text unless you pass raise_parsing_error=True, which raises NetmikoParsingException instead. Either way, write your code for the failure case.

TextFSM with ntc-templates

use_textfsm=True parses output with the community ntc-templates library and returns a list of dictionaries. It needs no extra package: ntc-templates and textfsm are installed with Netmiko 4.8.0. The field names come from the template for that command and platform. The exception message tells you whether the template was not found for the command and device type, or was found but did not match the output.

use_textfsm=True with raise_parsing_error=True returns a list of dictionaries
import os

from netmiko import ConnectHandler
from netmiko.exceptions import NetmikoParsingException

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

with ConnectHandler(**device) as conn:
    try:
        rows = conn.send_command(
            "show ip interface brief",
            use_textfsm=True,
            raise_parsing_error=True,
        )
    except NetmikoParsingException as err:
        print(f"Could not parse the output: {err}")
        raise
    for row in rows:
        print(row["interface"], row["ip_address"], row["status"])

Without raise_parsing_error, check the type of the result before you use it.

Without raise_parsing_error, check what came back
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

with ConnectHandler(**device) as conn:
    result = conn.send_command("show ip interface brief", use_textfsm=True)
    if isinstance(result, str):
        # No template matched, so Netmiko returned the raw text.
        print("Not parsed, raw output follows")
        print(result)
    else:
        for row in result:
            print(row["interface"], row["ip_address"], row["status"])

TTP

use_ttp=True parses output with a TTP template that you write, passed as ttp_template. It needs pip install ttp. The template path is resolved from where you run the script unless you give a full path, so the example builds the path from the script's own location. TTP returns nested lists: the list of dictionaries is at parsed[0][0].

use_ttp=True with a template file you write yourself
import os
from pathlib import Path

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

# interfaces.ttp sits in the same directory as this script.
template = Path(__file__).with_name("interfaces.ttp")

with ConnectHandler(**device) as conn:
    parsed = conn.send_command(
        "show ip interface brief",
        use_ttp=True,
        ttp_template=str(template),
        raise_parsing_error=True,
    )
    # TTP returns nested lists. The interface dictionaries are in parsed[0][0].
    for row in parsed[0][0]:
        print(row["interface"], row["status"], row["protocol"])

The template is a file named interfaces.ttp next to the script. Each {{ name }} marks a value to capture. Interface status can be more than one word, as in administratively down, so the status uses the ORPHRASE pattern, which captures a phrase rather than a single word.

interfaces.ttp
{{ interface }} {{ ip_address }} YES {{ method }} {{ status | ORPHRASE }} {{ protocol }}

Genie

use_genie=True parses output with the Genie parser library. It needs pip install genie pyats.

use_genie=True uses the Genie parser library
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

with ConnectHandler(**device) as conn:
    parsed = conn.send_command("show ip interface brief", use_genie=True)
    print(parsed)

Which one to pick

Start with TextFSM when ntc-templates already covers your command, as it is the lightest option and needs nothing extra. Choose TTP when you need to parse a command or a format that has no ready-made template, because the template is yours to write and adjust. Choose Genie if your team already works with the Genie and pyATS ecosystem, and accept that it brings a much larger set of dependencies. The TextFSM and TTP examples were run against sample output for a Cisco IOS interface table, using a stand-in connection, not a device.

Troubleshooting common Netmiko problems

Most first problems are one of four things: logging in, waiting too long, prompt matching, or parsing. The project's own COMMON_ISSUES.md covers more.

Authentication failures

A failed login raises NetmikoAuthenticationException. Check the username and password the script is actually reading from the environment, whether the account is locked, and whether the device needs an enable secret. Avoid retrying in a loop with credentials you know are wrong, as repeated failures can lock the account.

Connection timeouts

If the device does not answer, Netmiko raises NetmikoTimeoutException. That points to reachability, the SSH port, an ACL or firewall, or an SSH server that is slow to respond. conn_timeout (10 seconds by default), auth_timeout and banner_timeout (15 seconds by default) control how long Netmiko waits at each stage of the connection.

Tell authentication failures from connection failures
import os

from netmiko import ConnectHandler
from netmiko.exceptions import (
    NetmikoAuthenticationException,
    NetmikoTimeoutException,
)

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
    "conn_timeout": 20,
}

try:
    conn = ConnectHandler(**device)
except NetmikoAuthenticationException:
    print("Login failed: check the account, the password and any lockout.")
except NetmikoTimeoutException:
    print("No response: check reachability, port 22 and any ACL or firewall.")
else:
    print(conn.find_prompt())
    conn.disconnect()

Command read timeouts

A command that does not finish within read_timeout raises ReadTimeout. That is a different failure from a connection timeout: the session is up, and Netmiko did not see the end of the output. Either the command needs longer, so raise read_timeout, or Netmiko did not recognise the end of the output, which is a prompt matching problem.

A command read that runs out of time raises ReadTimeout
import os

from netmiko import ConnectHandler
from netmiko.exceptions import ReadTimeout

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
}

with ConnectHandler(**device) as conn:
    try:
        output = conn.send_command("show running-config", read_timeout=30)
    except ReadTimeout:
        # Either the command needs longer, or the end of the output was
        # not recognised (see prompt matching below).
        output = conn.send_command("show running-config", read_timeout=180)
    print(len(output.splitlines()), "lines")

Prompt matching

Netmiko finds the end of most output by matching the device's prompt. An unusual prompt, a banner, or the wrong device_type can stop that working, and the symptom is usually a ReadTimeout whose message says the pattern was not detected. Print the prompt Netmiko sees with find_prompt(), confirm the device_type, and keep a session_log to see exactly what the device sent. Where the output really ends with something other than the prompt, expect_string can name the ending.

Look at the prompt and keep a session log while debugging
import os

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios",
    "host": "192.0.2.10",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
    # The log can contain sensitive output. Handle it like a configuration.
    "session_log": "netmiko-session.log",
}

with ConnectHandler(**device) as conn:
    # If this does not look like your device's real prompt, prompt matching
    # will be unreliable. Check the device_type first.
    print(repr(conn.find_prompt()))

Parsing fallback and missing templates

With use_textfsm=True, a command without a matching template returns the raw text, so a loop over the result can fail with confusing errors. Pass raise_parsing_error=True to get NetmikoParsingException, whose message says whether the template was not found or did not match the output. If a template is missing, ntc-templates may have a newer one, you can point the NET_TEXTFSM environment variable at your own template directory, or you can write a TTP template. The parsing examples above show the checks to put around the result.

Netmiko vs Paramiko, NAPALM, Nornir, Scrapli and Ansible

These tools are mostly layers that work together, not rivals. A typical stack uses several of them. The table summarises each project's own description, and the notes below it say where each one fits with Netmiko.

Netmiko, Paramiko, NAPALM, Nornir, Scrapli and Ansible compared
ToolWhat it isLayerConnects withRunning many devicesTypical use
ParamikoPure-Python SSHv2 implementation, client and serverTransportSSHOut of scopeSSH client or server code of any kind
NetmikoMulti-vendor library for CLI sessions to network devicesDevice connectionSSH, Telnet and serial, with SSH through ParamikoOne connection per object. Via your own code or a framework such as NornirScripts that run commands and push configuration
NAPALMUnified API across several network operating systemsAbstraction over per-vendor driversPer-vendor drivers, with Netmiko among its dependenciesNot evaluated hereRetrieving data and loading configuration through one API
NornirPython automation framework with inventory managementOrchestrationVia plugins, for example nornir_netmikoMulti-threaded task dispatch across an inventoryRunning tasks across many devices
Scrapli (original Python library)Python screen-scraping client for network devicesDevice connectionTelnet or SSH, with pluggable transportsSync and asyncio useScripts that want sync or asyncio connections
AnsibleAgent-less automation driven by YAML playbooksAutomation platformVia connection plugins: network_cli (CLI over SSH), netconf, httpapiTasks run against the hosts in an inventoryPlaybook-driven automation across a mixed estate

“Out of scope” means the project's own description does not set out to do it. “Via” means it is supplied through plugins, connection types or surrounding tools. “Not evaluated here” means this guide has not checked that detail. The Scrapli row describes the original Python library as released on PyPI (2026.2.20 at the time of writing).

Netmiko vs Paramiko

Paramiko is a pure-Python implementation of the SSHv2 protocol. Netmiko is built on it, so this is a layering rather than a choice. Use Paramiko directly when you need SSH for any purpose. Use Netmiko when the other end is a network device and you want prompt and configuration mode handling done for you.

Netmiko vs NAPALM

NAPALM offers a unified API across several network operating systems, with getters for retrieving data and methods to load a configuration as a merge or a replace. Netmiko gives you the CLI session itself. NAPALM suits work its API already covers, and Netmiko suits a command or platform it does not expose. NAPALM lists Netmiko among its dependencies, so the two often meet in the same environment.

Nornir vs Netmiko

Nornir is an automation framework written in Python. It looks after the inventory and dispatches tasks to devices. It does not replace Netmiko: the nornir_netmiko plugin runs Netmiko inside Nornir tasks, with Nornir handling the inventory and the fan-out across hosts. For how Nornir works in its own right, read the Nornir guide.

Netmiko vs Scrapli

Scrapli is a screen-scraping client for network devices that works over Telnet or SSH, supports both sync and asyncio use, and has a pluggable transport system. It covers similar ground to Netmiko. If your code is built around asyncio, Scrapli's async support is worth a look. The project's documentation site now also describes a newer unified generation built on libscrapli, which this guide does not cover, so check which generation you are using before you compare details. Whichever you choose, check each project's platform list against the devices you run.

Netmiko vs Ansible

Ansible is agent-less automation driven by YAML playbooks. Its network automation supports more than one connection type: network_cli runs CLI commands over SSH, and netconf and httpapi cover NETCONF and HTTP APIs, depending on the platform and the modules you use. The choice between Ansible and Netmiko is mostly about how your team prefers to write and review automation, and the two are not mutually exclusive.

What it takes to run Netmiko at enterprise scale

Netmiko is a library, and it is intentionally scoped as one: it does its job of talking to devices very well. Running automation across a whole estate means providing, integrating or maintaining several capabilities around it. Many organisations already have some of them, such as an inventory, a scheduler, a secret store, an identity provider and central logging, so the work is often integration, not building from scratch.

The list below is a set of requirements to check, not a build plan. Which ones apply depends on your estate, your auditors and your team, and none of them is a flaw in Netmiko.

  • Device inventory

    Automation needs to know which devices exist, their platforms and their addresses. You may already have an inventory, a CMDB or a source of truth to draw on. Whether it is a file, an existing system or a framework such as Nornir, somebody has to keep it correct as devices are added, moved and retired.

  • Concurrency and rate limiting

    Netmiko manages one connection per object, so running across many devices means threads, processes or a framework such as Nornir. The requirement is to decide how many sessions run at once, and to avoid overloading AAA servers or the devices themselves.

  • Scheduling

    Regular collection needs a scheduler, retries for devices that are unreachable, and a way to see that a run finished. Cron, a CI system or an orchestrator you already run may cover the scheduling, leaving the retries and the visibility to integrate.

  • Configuration versioning

    Collecting a configuration is one part of the workflow. The requirement is to keep each version with its device and time, and to keep that history safe and searchable. A Git repository, an existing archive or a dedicated tool can all fill that role.

  • Diffing

    Engineers want to see what changed, not just what exists. That means comparing versions sensibly, ignoring noise such as timestamps and counters, and presenting the result in a way people will read.

  • Compliance checks

    Checking configurations against policy means defining the rules, running them on each collection and reporting results by device and by rule. Where an auditor needs the result, it should be repeatable.

  • Role-based access

    Once more than one person uses the automation, who may run what, and against which devices, needs an answer. Permissions you already have in a CI system or on a jump host may provide part of it.

  • Sign-in for people (SSO and RADIUS)

    If your automation is a service that people use, they will usually sign in through the identity provider or RADIUS server you already run. This is separate from how the automation signs in to your network devices, which is covered below.

  • Audit trail

    Auditors may ask who ran what, against which device, and with what result. Existing logging infrastructure, CI job history or a SIEM may already capture some of it. The requirement is that the evidence exists and is retained for as long as you need it.

  • Credentials for devices, and rotation

    Reading device credentials from the environment is the right start. At scale the requirement is a secret store, accounts scoped per device or group, and a way to rotate credentials without breaking the scheduled jobs that use them. Where devices use TACACS+ or RADIUS accounts, that is part of this too.

  • High availability

    If collection runs from one host, that host becomes part of the operations process. Your existing platform for scheduled jobs may already be resilient, and the requirement is to make sure the automation benefits from it.

  • Ownership of breakage

    Vendors change a prompt, a banner or the output of a command between OS releases, and automation that worked yesterday can need attention today. Somebody has to own noticing, fixing and testing against each new release.

When it makes sense to stop building

This is not an argument against Netmiko. It is a good fit for one-off jobs, custom workflows and ongoing automation, and plenty of teams keep their scripts running alongside a platform for exactly those cases.

Whether to keep building, adopt a platform for part of the work, or do both depends on your situation: your requirements, the engineering capacity you can give the work, what the surrounding capabilities cost to maintain, and the infrastructure you already have. If your inventory, scheduler, secret store and logging already meet the requirements in the previous section, extending your scripts may be the best answer.

These questions can help you decide:

  • Does an auditor need evidence that your scripts do not produce today?
  • Is maintaining the surrounding capabilities taking more of your time than the automation itself?
  • Do several teams need controlled access to the same collected configurations?
  • Is the work described in the previous section growing faster than the capacity you have to build and maintain it?

If the answers point that way, a platform that supplies some of those capabilities may be worth evaluating next to what you already run.

Using Netmiko alongside rConfig

“Alongside” means the two can coexist. rConfig can take on the configuration management responsibilities described above, and your custom Python workflows can keep using Netmiko. This guide does not describe a native integration between them.

What rConfig can cover, by edition

Not every edition includes every capability. This is how the current edition comparison on the rConfig site divides the capabilities discussed in this guide.

rConfig capabilities by edition, as listed in the edition comparison
EditionAdds
rConfig Core (free, open source)Scheduled backups, Compare configuration versions, Single sign-on
Starter and abovePolicy checks and compliance status, Role-based access control, RADIUS authentication, LDAP and Active Directory, User audit log, Restore a previous configuration, Push configuration changes
Standard and aboveCheck compliance continuously, Evidence you can hand to an auditor
Enterprise / MSPDistributed collectors, High availability, Manage every rConfig instance centrally

What stays with Netmiko

rConfig does not claim to match Netmiko for scripted change. Custom workflows, one-off jobs and unusual platforms are where Netmiko remains the right tool, and teams can keep it for those while rConfig handles collection, history and review. See how automation and configuration management differ and the rConfig solution overview.

Evaluate rConfig Core alongside your existing scripts

rConfig V8 Core is free and open source, with no time limit and no device limit, so you can run it next to your scripts and see which of the requirements above it covers for your estate. If you want to talk through the paid editions, you can book a demo.

Netmiko FAQ

What is Netmiko used for?

Netmiko is used to connect to network devices from Python, run show commands and push configuration. It handles prompts, paging and configuration modes, so a script can collect output, make changes and save configuration across many vendors.

Is Netmiko a Python library?

Yes. Netmiko is an open source, multi-vendor Python library, maintained by Kirk Byers and built on top of Paramiko. You install it with pip and import it into your own Python scripts.

What is the difference between Netmiko and Paramiko?

Paramiko is a pure-Python implementation of the SSHv2 protocol. Netmiko uses Paramiko as its SSH transport and adds network device behaviour on top, such as prompt handling, configuration modes and per-platform drivers.

How do I install Netmiko?

Create a virtual environment and run pip install netmiko. Netmiko 4.8.0 needs Python 3.10 or newer, below 4.0. Credentials are best read from environment variables, and for production you should also verify SSH host keys.

What device types does Netmiko support?

The device_type value selects the driver, for example cisco_ios, cisco_xe, cisco_nxos, cisco_xr, juniper_junos or arista_eos. At the time of writing, PLATFORMS.md at release v4.8.0 lists 8 regularly tested platforms, 61 with limited testing and 68 experimental. The live list on GitHub follows the development branch and can differ from the release.

Is Netmiko free?

Yes. Netmiko is open source and released under the MIT licence.

Does Netmiko support Telnet?

Yes. Netmiko supports Telnet and serial connections as well as SSH, but only for the platforms that have a Telnet or serial device type in its mapping. Mapped in Netmiko 4.8.0 are 62 Telnet device types, such as cisco_ios_telnet and arista_eos_telnet, and two serial ones, cisco_ios_serial and furukawa_fitelnet_serial. Adding a suffix to another device type does not create support. Use SSH where the device supports it.

Should I use Netmiko or Ansible?

They are not mutually exclusive. Netmiko is a Python library for scripts that need exact control of each CLI session. Ansible is agent-less automation driven by YAML playbooks, and its network automation can use the network_cli connection (CLI over SSH), netconf or httpapi, depending on the platform and modules. Choose by how your team prefers to write and review automation.

Netmiko or NAPALM: which should I use?

NAPALM offers one API across several network operating systems, with getters and configuration merge and replace. Netmiko gives you the CLI session itself. Use NAPALM for work its API already covers, and Netmiko for a command or platform it does not expose. NAPALM lists Netmiko among its dependencies, so the two often appear together.

How do Nornir and Netmiko work together?

Nornir is an automation framework that manages inventory and dispatches tasks to devices. The nornir_netmiko plugin runs Netmiko inside Nornir tasks, so Netmiko handles each device connection while Nornir handles the inventory and the work across many hosts. The Nornir guide on this site covers inventory, tasks and plugins in more detail.

Can I use Netmiko alongside rConfig?

Yes, they can run side by side. Teams keep Netmiko for one-off jobs, custom workflows and unusual platforms, and can use rConfig for configuration management. rConfig Core is free and open source and covers scheduled backups, comparison between configuration versions and single sign-on. Compliance policy checks, role-based access control, RADIUS sign-in and a user audit log are in the paid editions from Starter, scheduled compliance checks and reports from Standard, and distributed collection and high availability in Enterprise and MSP. This guide does not describe a native integration between rConfig and Netmiko.

Further reading and credit

Netmiko

On rConfig

Thank you to Kirk Byers and to the contributors who have built and maintained Netmiko for the whole community.