Python Netmiko Guide: Automating Cisco, Juniper, and MikroTik Network Devices

The Nightmare of Manual CLI Operations and Approaches to Network Automation

Every network engineer has probably experienced this nightmare: 11 PM, juggling dozens of PuTTY tabs just to update a single access-list entry, change an NTP server IP, or back up configurations across 50 branch switches.

Much like shifting to automation instead of endless clicking in IT administration, manual execution is tedious and carries high risks. A single typo in an IP octet, forgetting to run write memory, or a connection drop mid-terminal paste can instantly isolate an entire branch office. To break free from this grind, network teams typically adopt one of four approaches:

  1. Bash Scripts with Expect or Raw Paramiko: Establish direct SSH sessions and push raw strings into the device CLI.
  2. Ansible Network: Declarative management using YAML playbooks via vendor-certified collections.
  3. Netmiko (Python): A purpose-built library built on top of Paramiko, specifically tailored for the CLIs of over 100 network operating systems.
  4. RESTCONF / NETCONF (YANG Data Models): Structured API communication via JSON/XML on modern enterprise hardware.

Pros and Cons of Each Approach

Each tool has its place, but their implementation complexity varies significantly in real-world environments:

  • Raw Paramiko:
    • Pros: Extremely lightweight with minimal external dependencies.
    • Cons: Painful terminal handling. You must manually catch pagination prompts like --More--, insert arbitrary time.sleep() calls to wait for buffers, and handle device-specific prompts (#, >, (config)#). If a command dumps 5,000 log lines, the script easily hangs.
  • Ansible:
    • Pros: Agentless, exceptionally powerful for large-scale configuration enforcement, with built-in idempotency (making changes only when drift occurs).
    • Cons: Heavyweight for quick ad-hoc queries. When dealing with legacy firmware or niche vendors, writing custom Jinja/Ansible filters is far more cumbersome than writing pure Python.
  • RESTCONF / NETCONF:
    • Pros: Delivers clean, structured JSON/XML data with zero risk of breaking changes caused by vendor CLI output formatting updates.
    • Cons: Steep hardware barriers. Many legacy enterprise switches like Cisco Catalyst 2960 or ISR G2 routers lack support or require costly licensing upgrades.
  • Netmiko:
    • Pros: Solves all raw Paramiko headaches out of the box. It automatically detects prompts, disables terminal pagination (terminal length 0), and transitions seamlessly between user, enable, and config modes. Support extends across Cisco (IOS, NX-OS), Juniper (JunOS), MikroTik (RouterOS), Arista, Fortinet, and beyond.
    • Cons: Inherently relies on text parsing. If a vendor alters CLI display formatting in a major firmware update, you must adjust your regex or parsing logic.

Why Systems Engineers Favor Netmiko

In practice, homogeneous single-vendor network infrastructures are rare. A data center might run Juniper SRX firewalls and Cisco Catalyst core switches, while branch edge routers are cost-effective MikroTik CCRs. In such heterogeneous multi-vendor environments, Netmiko is consistently the fastest, most pragmatic lifesaver.

It is lightweight enough to embed into a periodic cron script, yet intelligent enough to handle buffer delays and timeouts automatically. Crucially, you retain full Python control: parse an Excel sheet of 200 device IPs, audit port statuses alongside inspecting real-time network traffic with Pyshark, and instantly dispatch alerting notifications to Telegram.

Step-by-Step Netmiko Hands-On Tutorial

1. Set Up a Virtual Environment

Netmiko runs best on Python 3.8 through 3.12. Create a dedicated virtual environment to prevent package conflicts with system packages:

# Create and activate the virtual environment
python3 -m venv netauto-env
source netauto-env/bin/activate

# Install the latest Netmiko release
pip install --upgrade netmiko

2. Run Show Commands on a Cisco IOS Switch

The send_command() method sends inspection commands and automatically waits for the complete output stream based on trailing prompt detection (in production, make sure to manage secrets like a DevOps pro rather than hardcoding passwords or storing them in plain environment variables):

import os
from netmiko import ConnectHandler
from netmiko.exceptions import NetmikoTimeoutException, NetmikoAuthenticationException

cisco_switch = {
    'device_type': 'cisco_ios',
    'host': '192.168.10.2',
    'username': os.getenv('NET_USER', 'admin'),
    'password': os.getenv('NET_PASS', 'StrongPassword123'),
    'secret': os.getenv('NET_ENABLE', 'EnablePassword123'),
}

try:
    print(f"Connecting to {cisco_switch['host']}...")
    with ConnectHandler(**cisco_switch) as net_connect:
        # Enter privileged EXEC mode
        net_connect.enable()
        
        # Send command to inspect interface IP addresses
        output = net_connect.send_command("show ip interface brief")
        print("--- DEVICE OUTPUT ---")
        print(output)

except NetmikoTimeoutException:
    print(f"Error: Connection to {cisco_switch['host']} timed out.")
except NetmikoAuthenticationException:
    print(f"Error: Invalid credentials for {cisco_switch['host']}.")
except Exception as e:
    print(f"An error occurred: {e}")

3. Push Batch Configurations to MikroTik RouterOS

MikroTik’s CLI uses a hierarchical menu structure with prompts formatted as [admin@MikroTik] >. Netmiko provides a dedicated mikrotik_routeros driver that reliably executes command batches via send_config_set():

from netmiko import ConnectHandler

mikrotik_router = {
    'device_type': 'mikrotik_routeros',
    'host': '192.168.88.1',
    'username': 'admin',
    'password': 'MikroTikSecretPassword',
    'port': 22,
}

# Configuration commands to apply
config_commands = [
    '/ip pool add name=pool-vpn ranges=172.16.100.10-172.16.100.50',
    '/ip firewall filter add chain=input protocol=icmp action=accept comment="Allow Ping"',
    '/system ntp client set enabled=yes servers=vn.pool.ntp.org'
]

with ConnectHandler(**mikrotik_router) as net_connect:
    print("Connected to MikroTik. Pushing configuration set...")
    output = net_connect.send_config_set(config_commands)
    print(output)
    print("MikroTik configuration completed!")

4. Safely Manage Configurations on Juniper JunOS

JunOS features a strict candidate configuration and commit workflow. Netmiko provides dedicated methods to perform syntax validation before applying changes live:

from netmiko import ConnectHandler

juniper_device = {
    'device_type': 'juniper_junos',
    'host': '10.0.0.1',
    'username': 'noc_admin',
    'password': 'JuniperPassword',
}

commands_to_apply = [
    'set system services ssh root-login deny',
    'set snmp location "Datacenter HaNoi Rack A1"'
]

with ConnectHandler(**juniper_device) as net_connect:
    # Enter candidate config mode
    net_connect.config_mode()
    
    # Load set commands
    net_connect.send_config_set(commands_to_apply, exit_config_mode=False)
    
    # Validate configuration syntax before committing
    check_result = net_connect.commit(check=True)
    print(f"Commit check result: {check_result}")
    
    # If valid, commit with an audit log message
    net_connect.commit(comment="Automated security hardening via Netmiko")
    net_connect.exit_config_mode()
    print("Configuration committed successfully to Juniper!")

5. Three Essential Tips for Production Deployments

When scaling your automation scripts from 1 to 100 devices and choosing how to manage Python configs with YAML, TOML, and INI for inventory management, keep these critical best practices in mind:

  • Multithreading with Concurrent Futures: Avoid sequential for loops. Processing 100 switches sequentially can take around 20 minutes, whereas using concurrent.futures.ThreadPoolExecutor(max_workers=20) cuts total execution time down to under 1 minute.
  • Proactively Increase Timeouts: Resource-intensive commands such as show tech-support or full BGP routing table lookups (900,000+ prefixes) require higher timeout thresholds: send_command(cmd, read_timeout=90) to prevent sudden session disconnects.
  • Leverage TextFSM for Data Parsing: Instead of crafting fragile custom regular expressions, pass use_textfsm=True to send_command(). Netmiko automatically parses unstructured CLI output tables into structured Python dictionaries.
Share: