Languages

Configuring Network Devices Using Python: A Beginner’s Guide

JGJaya Gupta21 Mar 2023 ยท Updated 04 Oct 2026 ยท 8 min read
Configuring Network Devices Using Python: A Beginner’s Guide

Quick answer: To configure network devices with Python you connect over SSH, send configuration commands, verify the result and save. Netmiko’s send_config_set() is the fastest way to push a handful of lines to a Cisco, Juniper or Arista device; NAPALM adds a safer workflow with a candidate configuration, a diff you can review and a commit or rollback. Both are a pip install away and work with Python 3.

Configuration is where automation delivers its biggest payoff โ€” and its biggest risk. This beginner’s guide takes you from a first loopback interface to a template-driven, diff-reviewed change process. You will learn the two main libraries, how to render configs from templates, how to verify what you pushed, and the seven mistakes that turn a time-saving script into a Saturday-night outage.

What “configuring” a device means to a script

When you configure a switch by hand you type configure terminal, enter commands, type end, then write memory. A script does exactly the same thing, so it helps to be explicit about the stages:

  1. Connect and, if needed, enter privileged (enable) mode.
  2. Enter configuration mode.
  3. Send commands โ€” one line at a time, in order.
  4. Exit configuration mode.
  5. Verify the running config or operational state.
  6. Save to startup config so the change survives a reboot.

Netmiko handles stages 1โ€“4 inside a single method call; you add 5 and 6. NAPALM inserts a review step between 3 and 4. If the connection layer is new to you, read Using Paramiko and Netmiko for SSH and Telnet Connections in Python first.

Netmiko versus NAPALM for configuration

Capability Netmiko NAPALM
Send a list of CLI commands send_config_set() load_merge_candidate(config=...)
Send a config file send_config_from_file() load_merge_candidate(filename=...)
Replace the entire config Manual load_replace_candidate()
Preview diff before applying No compare_config()
Commit / discard Immediate commit_config() / discard_config()
Automatic rollback timer No commit_config(revert_in=300) on supported platforms
Vendor coverage 100+ platforms IOS, IOS-XR, NX-OS, Junos, EOS (core drivers)
Best for Quick changes, unusual vendors Reviewed, repeatable changes on major platforms

Install both with pip install netmiko napalm inside a virtual environment.

Hands-on: your first configuration push with Netmiko

The example creates a loopback interface on a Cisco IOS router, verifies it, and saves. Credentials are read from environment variables (export NET_USER=admin) so nothing sensitive lives in the file.

import os
from netmiko import ConnectHandler

router = {
    "device_type": "cisco_ios",
    "host": "192.168.1.1",
    "username": os.environ["NET_USER"],
    "password": os.environ["NET_PASS"],
    "secret": os.environ.get("NET_ENABLE", ""),
}

config_commands = [
    "interface Loopback0",
    " description Management loopback - pushed by Python",
    " ip address 10.0.0.1 255.255.255.255",
    " no shutdown",
]

with ConnectHandler(**router) as conn:
    conn.enable()
    print(conn.send_config_set(config_commands))

    # Verify before you trust it
    verify = conn.send_command("show ip interface brief | include Loopback0")
    print(verify)
    if "10.0.0.1" not in verify:
        raise SystemExit("Loopback0 did not get its address - not saving")

    print(conn.save_config())

send_config_set() enters and exits configuration mode for you and returns the device echo, which is useful for logging. The verification step is not optional in production: a mistyped command is silently rejected by the device with a % Invalid input message that only appears in that echo.

For longer changes keep the commands in a text file and call conn.send_config_from_file("loopback.cfg"). The file is easier to review in a pull request than a Python list.

Hands-on: a safer workflow with NAPALM

NAPALM treats configuration like a database transaction. You load a candidate, compare it with the running config, and commit only if the diff is what you expected.

import os
from napalm import get_network_driver

driver = get_network_driver("ios")
device = driver(
    hostname="192.168.1.1",
    username=os.environ["NET_USER"],
    password=os.environ["NET_PASS"],
    optional_args={"secret": os.environ.get("NET_ENABLE", "")},
)

candidate = """
interface Loopback0
 description Management loopback - pushed by NAPALM
 ip address 10.0.0.1 255.255.255.255
"""

device.open()
device.load_merge_candidate(config=candidate)
diff = device.compare_config()

if not diff:
    print("Device already matches - nothing to do")
    device.discard_config()
else:
    print("Proposed change:\n", diff)
    if input("Commit? (yes/no): ").strip().lower() == "yes":
        device.commit_config()
        print("Committed and saved")
    else:
        device.discard_config()
        print("Discarded")

device.close()

Two behaviours stand out. First, the script is idempotent: run it twice and the second run reports no diff and changes nothing, which is exactly what you want in scheduled automation. Second, commit_config() on the IOS driver also saves to startup config, so there is no separate save step. On platforms with native commit support such as Junos and IOS-XR, you can pass revert_in=300 and the device will roll back automatically if you lose connectivity and do not confirm within five minutes.

If your change replaces an entire device configuration โ€” for example, standardising a batch of new access switches โ€” use load_replace_candidate(filename="golden.cfg") instead. The diff shows every line that will be removed as well as added, which makes a dangerous operation reviewable.

Hands-on: generating configuration from a template

Hand-writing commands for each device does not scale. Jinja2 (pip install jinja2) renders a template with per-device variables, and the output feeds straight into either library.

import os
from jinja2 import Template
from netmiko import ConnectHandler

TEMPLATE = Template("""
hostname {{ hostname }}
interface Loopback0
 ip address {{ loopback }} 255.255.255.255
{% for vlan in vlans %}
vlan {{ vlan.id }}
 name {{ vlan.name }}
{% endfor %}
""".strip())

sites = [
    {"host": "192.168.1.1", "hostname": "BLR-SW01", "loopback": "10.0.0.1",
     "vlans": [{"id": 10, "name": "USERS"}, {"id": 20, "name": "VOICE"}]},
    {"host": "192.168.1.2", "hostname": "BLR-SW02", "loopback": "10.0.0.2",
     "vlans": [{"id": 10, "name": "USERS"}]},
]

for site in sites:
    rendered = TEMPLATE.render(**site)
    print(f"--- {site['hostname']} ---\n{rendered}\n")   # review before pushing

    conn = ConnectHandler(
        device_type="cisco_ios", host=site["host"],
        username=os.environ["NET_USER"], password=os.environ["NET_PASS"],
    )
    conn.send_config_set(rendered.splitlines())
    conn.save_config()
    conn.disconnect()

In a real project the sites list comes from a YAML file, a CSV export or a source-of-truth system such as NetBox. The template lives in Git, is reviewed like code, and is the single place a standard gets changed. This is the same model Ansible uses under the hood; the broader picture is in Why Use Python for Network Engineering?.

Seven configuration mistakes beginners make

  1. Not verifying. The device echo contains % Invalid input detected when a command fails, and the script continues happily. Read the output and check state afterwards.
  2. Forgetting to save. send_config_set() changes the running config only. Without save_config() the next reboot erases everything.
  3. No pilot device. Run the change against one device, inspect it, then expand. A loop over 200 devices multiplies mistakes as efficiently as it multiplies successes.
  4. Locking yourself out. Changing the VTY ACL, management VLAN or default route on the path you are connected through drops the session mid-change. Use NAPALM’s revert_in, or on IOS schedule reload in 10 before the change and reload cancel after verifying.
  5. Credentials in the script. Use environment variables, getpass, SSH keys or a secrets manager. A password in a Git history is permanent.
  6. Ignoring exceptions. Wrap connections in try/except for NetmikoTimeoutException and NetmikoAuthenticationException, log the failed hosts, and report them at the end instead of crashing or silently skipping.
  7. Mixing vendors in one command list. interface range is IOS syntax; Junos wants set interfaces .... Keep one template per platform and select it by device_type.

Frequently asked questions

Does send_config_set() save the configuration?

No. It only modifies the running configuration. Call save_config() afterwards, or pass the commands through NAPALM’s commit_config(), which saves on IOS.

Can I roll back a Netmiko change?

Not automatically. Capture show running-config before the change and keep it; if something goes wrong, push the relevant no commands or use configure replace with the saved file on IOS. NAPALM’s rollback() handles this for you after a commit.

Which is better for a beginner, Netmiko or NAPALM?

Start with Netmiko to understand what the device sees. Move to NAPALM as soon as you push changes to more than a few devices โ€” the diff review is worth the slightly steeper learning curve.

Can I configure devices over an API instead of SSH?

Yes. Modern IOS-XE, NX-OS, Junos and EOS support RESTCONF, NETCONF or vendor REST APIs with structured data. The requests and ncclient libraries handle these, and NAPALM uses them internally on some platforms. SSH remains the universal fallback for older gear.

Key takeaways

  • Configuration automation follows the same stages as manual work: connect, configure, verify, save.
  • Netmiko’s send_config_set() is the quickest route; NAPALM adds diff review, idempotent commits and rollback.
  • Generate configs from Jinja2 templates and device data, never by hand per device.
  • Always pilot, verify the echo, save, handle exceptions, and keep credentials out of code.

Ready to build Python skills that go beyond scripts into full automation and data projects? Our Python & Data Science course covers Python from the ground up with hands-on labs, mentor support and placement assistance. For live configuration demos, subscribe to our YouTube channel.

JG
Written byJaya Gupta

Part of the Techknowledgehub team of industry mentors, writing practical guides to help you build a job-ready tech career.

More articles by Jaya Gupta โ†’
Keep reading

Related articles

M# Programming Language
Languages

M# Programming Language

What the M# programming language really is, how its declarative models generate C# and ASP.NET Core code, who uses it,โ€ฆ

01 Apr 2023ยท 8 min read

Leave a Reply