Network Automation 〡 Tutorials

Complete Guide to Nornir Network Automation for Engineers

Netodata
December 13, 2025

Table Of Contents

What is Nornir and Why It Matters in Network Automation

At its core, Nornir is a Python-based network automation framework designed specifically for engineers who need fine-grained control of the automation logic. Unlike Ansible, which is declarative and YAML-heavy, Nornir is imperative, giving users the ability to write and control their workflows in Python.

Why is Nornir gaining traction in enterprise environments? Because it enables automation at scale without sacrificing flexibility. Network engineers can integrate with Python libraries like Netmiko, Napalm, or even custom APIs, all while maintaining consistent data structures and threading support.

  • Python-native: Ideal if you’re already comfortable scripting in Python.
  • Multithreaded execution: Speed up processing for large device inventories.
  • Highly extensible: Customize inventory, plugins, and logging to suit enterprise-grade needs.
  • Plugin-based architecture: Easily interface with other tools and modules.

Nornir fills the gap between low-level scripting and high-level playbook-driven automation, offering a middle ground where performance and control live in harmony. For full documentation, visit the official Nornir documentation.

Nornir vs. Ansible and Netmiko: Which Does What Best?

To understand why you might select Nornir over Ansible or Netmiko, we need to examine the nuances in their design and purpose.

Ansible

  • Strengths: Excellent when used for configuration management with YAML playbooks. Easy to pick up for small-scale automation.
  • Limitations: Not ideal for complex logic, lacks advanced concurrency without additional setup.

Explore more in the official Ansible documentation.

Nornir

  • Strengths: Built-in support for parallelism and inventory management makes it suitable for large-scale tasks. If you’re already comfortable with Python, Nornir can offer precise control without the abstraction layer that tools like Ansible introduce.
  • Limitations: Comes with a steeper learning curve compared to tools like Ansible. Since it’s code-centric, it may not be ideal for teams without Python expertise. Lacks the rich ecosystem of modules and community roles that Ansible has.

Netmiko

  • Strengths: A lightweight Python library focused on SSH-based CLI automation. It’s quick to get started with and ideal for simple device configuration tasks or retrieving operational data from network devices. Often used as a building block within more comprehensive tools or scripts.
  • Limitations: Netmiko isn’t a framework; it lacks inventory management, parallel execution, or native task orchestration. Users need to implement their own structure for more complex workflows. Best used for straightforward, direct CLI interactions.

Getting Started with Nornir Network Automation: Step-by-Step Guide

Introduction

Nornir is a powerful Python automation framework designed specifically for network professionals. Unlike other automation tools that use their own domain-specific language, Nornir leverages pure Python, making it flexible, extensible, and ideal for complex enterprise network automation tasks.

Prerequisites

Before starting with Nornir, ensure you have:

  • Python 3.7 or newer installed
  • Basic understanding of Python programming
  • Access to network devices for testing

Installation Process

Step 1: Set Up a Virtual Environment

It’s recommended to use a virtual environment to keep your Nornir project dependencies isolated:

# Create a virtual environment
python -m venv nornir-env

# Activate the environment
# On Linux/Mac:
source nornir-env/bin/activate
# On Windows:
nornir-env\Scripts\activate

Step 2: Install Nornir and Essential Plugins

# Install core Nornir package
pip install nornir

# Install commonly used plugins
pip install nornir-netmiko  # For connecting to devices
pip install nornir-napalm   # For multi-vendor support
pip install nornir-utils    # For utility functions
pip install nornir-jinja2   # For templating

Project Structure Setup

Create a basic project structure as follows:

nornir-project/
├── inventory/
│   ├── hosts.yaml
│   ├── groups.yaml
│   └── defaults.yaml
├── templates/
│   └── vlan_config.j2
├── config.yaml
└── scripts/
    ├── configure_vlans.py
    └── backup_configs.py

Inventory Configuration

Define hosts.yaml file

This file defines all your network devices:

# inventory/hosts.yaml
router1:
  hostname: 192.168.1.1
  groups:
    - cisco_ios
  data:
    role: core_router
    location: main_datacenter

switch1:
  hostname: 192.168.1.2
  groups:
    - cisco_ios
  data:
    role: access_switch
    location: floor1

switch2:
  hostname: 192.168.1.3
  groups:
    - cisco_ios
  data:
    role: access_switch
    location: floor2

Define groups.yaml file

This file defines device groups with shared attributes:

# inventory/groups.yaml
cisco_ios:
  platform: ios
  connection_options:
    netmiko:
      extras:
        device_type: cisco_ios
        timeout: 120
    napalm:
      extras:
        optional_args:
          transport: telnet

arista_eos:
  platform: eos
  connection_options:
    netmiko:
      extras:
        device_type: arista_eos
    napalm:
      extras:
        optional_args: {}

Define defaults.yaml file

This file sets default parameters for all devices:

# inventory/defaults.yaml
username: admin
password: secure_password
connection_options:
  netmiko:
    extras:
      timeout: 60

Define config.yaml file

This file defines the Nornir configuration:

# config.yaml
inventory:
  plugin: SimpleInventory
  options:
    host_file: "inventory/hosts.yaml"
    group_file: "inventory/groups.yaml"
    defaults_file: "inventory/defaults.yaml"

runner:
  plugin: threaded
  options:
    num_workers: 10

Basic Automation Script Examples

Example 1: Configure VLANs

Create a script to configure VLANs on all switches:

# scripts/configure_vlans.py
from nornir import InitNornir
from nornir_netmiko import netmiko_send_config
from nornir_utils.plugins.functions import print_result
from nornir.core.filter import F

# Initialize Nornir with our config file
nr = InitNornir(config_file="config.yaml")

# Filter for only access switches
switches = nr.filter(F(data__role="access_switch"))

# Define the configuration commands
vlan_config = [
    "vlan 100",
    "name Engineering",
    "vlan 200",
    "name Sales",
    "vlan 300",
    "name Management",
    "exit"
]

# Execute the configuration on all devices
results = switches.run(
    task=netmiko_send_config,
    config_commands=vlan_config
)

# Print the results
print_result(results)

Example 2: Backup Device Configurations

Create a script to backup configurations from all devices:

# scripts/backup_configs.py
from nornir import InitNornir
from nornir_netmiko import netmiko_send_command
from nornir_utils.plugins.functions import print_result
import os
from datetime import datetime

# Initialize Nornir
nr = InitNornir(config_file="config.yaml")

# Create backups directory if it doesn't exist
backup_dir = "backups"
if not os.path.exists(backup_dir):
    os.makedirs(backup_dir)

# Get current date for filename
date_str = datetime.now().strftime("%Y-%m-%d")

def backup_config(task):
    # Run "show running-config" command
    config_result = task.run(
        task=netmiko_send_command,
        command_string="show running-config"
    )

    # Create a location-specific directory
    device_location = task.host.get("location", "unknown")
    location_dir = f"{backup_dir}/{device_location}"
    if not os.path.exists(location_dir):
        os.makedirs(location_dir)

    # Save the config to a file
    filename = f"{location_dir}/{task.host.name}_{date_str}.txt"
    with open(filename, 'w') as f:
        f.write(config_result.result)

    return f"Backup saved to {filename}"

# Run the backup task on all devices
result = nr.run(task=backup_config)

# Print results
print_result(result)

Example 3: Using Templates for Configuration

First, create a Jinja2 template:

{# templates/vlan_config.j2 #}
{% for vlan in vlans %}
vlan {{ vlan.id }}
 name {{ vlan.name }}
{% endfor %}

Then create a script to use this template:

# scripts/template_config.py
from nornir import InitNornir
from nornir_jinja2.plugins.tasks import template_file
from nornir_netmiko import netmiko_send_config
from nornir_utils.plugins.functions import print_result
from nornir.core.filter import F

# Initialize Nornir
nr = InitNornir(config_file="config.yaml")

# Filter for only switches
switches = nr.filter(F(data__role="access_switch"))

# Define VLAN data
vlan_data = {
    "vlans": [
        {"id": 100, "name": "Engineering"},
        {"id": 200, "name": "Sales"},
        {"id": 300, "name": "Management"}
    ]
}

def configure_from_template(task):
    # Generate config from template
    template_result = task.run(
        task=template_file,
        path="templates",
        template="vlan_config.j2",
        **vlan_data
    )

    # Extract the rendered configuration
    config = template_result.result

    # Apply the configuration
    task.run(
        task=netmiko_send_config,
        config_commands=config.splitlines()
    )

# Run the configuration task
results = switches.run(task=configure_from_template)

# Print results
print_result(results)

Advanced Features and Best Practices

Error Handling

Implement proper error handling in your scripts:

from nornir.core.exceptions import NornirExecutionError

try:
    results = nr.run(task=configure_from_template)
    print_result(results)

    # Check for failures
    failed_hosts = [host for host, result in results.items() if result.failed]
    if failed_hosts:
        print(f"Configuration failed on hosts: {', '.join(failed_hosts)}")
except NornirExecutionError as e:
    print(f"Error occurred: {e}")

Parallel Execution Control

Adjust the number of concurrent connections based on your network capacity:

nr = InitNornir(
    config_file="config.yaml",
    runner={
        "plugin": "threaded",
        "options": {
            "num_workers": 20,  # Increase for larger networks
        },
    },
)

Handling Sensitive Data

Use environment variables for credentials:

# Import os module at the top
import os

# In your script
nr = InitNornir(config_file="config.yaml")

# Set credentials from environment variables
nr.inventory.defaults.username = os.environ.get("NET_USER")
nr.inventory.defaults.password = os.environ.get("NET_PASS")

Filtering Capabilities

Nornir offers powerful filtering to target specific devices:

# Filter by multiple criteria
floor1_cisco_devices = nr.filter(
    F(data__location="floor1") & 
    F(platform="ios")
)

# Filter by role and exclude specific hosts
core_devices_except_router1 = nr.filter(
    F(data__role="core_router") & 
    ~F(name="router1")
)

Real-World Use Cases

Network Compliance Checking

def check_compliance(task):
    # Check for approved NTP servers
    ntp_result = task.run(
        task=netmiko_send_command,
        command_string="show run | include ntp server"
    )

    approved_ntp = ["10.0.1.1", "10.0.1.2"]
    current_ntp = ntp_result.result

    compliant = all(server in current_ntp for server in approved_ntp)

    return {
        "compliant": compliant,
        "details": current_ntp
    }

results = nr.run(task=check_compliance)

Mass Configuration Deployment

For large-scale changes across the network:

def deploy_acl_changes(task):
    # First backup the current config
    backup_result = task.run(task=backup_config)

    # Deploy the new ACL
    acl_config = [
        "ip access-list extended UPDATED_ACL",
        "permit ip any host 10.0.0.1",
        "permit ip any host 10.0.0.2",
        "deny ip any any log"
    ]

    task.run(
        task=netmiko_send_config,
        config_commands=acl_config
    )

    # Verify the deployment
    verify_result = task.run(
        task=netmiko_send_command,
        command_string="show ip access-list UPDATED_ACL"
    )

    return {
        "backup": backup_result,
        "verification": verify_result.result
    }

results = nr.run(task=deploy_acl_changes)

Troubleshooting Common Issues

Connection Issues

If you encounter connection problems:

  1. Verify IP addresses in your inventory
  2. Check credentials
  3. Ensure proper transport protocol (SSH/Telnet)
  4. Adjust timeouts for slower devices
# Increase timeout for specific devices
nr.inventory.hosts["slow_device"].connection_options["netmiko"] = {
    "extras": {"timeout": 300}
}

Task Failures

When tasks fail:

  1. Enable verbose logging
  2. Inspect the error message carefully
  3. Run the task against a single device for debugging
import logging

# Enable logging
logging.basicConfig(
    filename="nornir.log",
    level=logging.DEBUG,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

# Test on a single device
test_device = nr.filter(name="router1")
result = test_device.run(task=problematic_task)

Conclusion

Nornir provides a powerful, flexible framework for network automation that can scale from simple tasks to complex enterprise deployments. By leveraging Python’s full capabilities, you can build sophisticated, maintainable automation solutions that integrate with the rest of your operational tools and processes.

As you grow more comfortable with Nornir, explore the rich ecosystem of plugins and contributors in the Nornir community. The official documentation and GitHub repositories are excellent resources for keeping up with the latest developments.

Additional Resources

Additional resources to help you get started.

Other Best Practices for Engineers Using Nornir

  • Modularize your code: Organize workflows into reusable Python modules and functions.
  • Use plugins wisely: Integrate with logging, error handling, and scheduling tools where needed.
  • Understand the threading model: Optimize the number of concurrent tasks based on your network device limitations.
  • Version control configurations: Integrate Nornir scripts with Git to enable audit trails and change management. Learn about Git-based workflows in the Red Hat GitOps overview.
  • Leverage community resources: The Nornir GitHub repository is rich with plugins, examples, and active discussions.

Where to Learn More and Find Community Support

  • Official Documentation: nornir.tech
  • GitHub Projects and Examples: Search Nornir on GitHub to see real projects.
  • Slack Channels and Forums: Join the “networktocode” Slack or Nornir community slack to discuss challenges.
  • Video Tutorials: YouTube has several walkthroughs by experienced network engineers.
  • Blog Series: Follow Netodata’s Blog for updates, guides, and enterprise insights on Nornir and network automation. You may also be interested in our internal post on infrastructure automation best practices for network managers.
NETWORK AUTOMATION INSIGHTS
Stay informed about the latest in network automation:
Technical deep dives - Implementation guides -
Industry best practices
Netodata official logo featuring a stylized green geometric icon and the brand name "NETODATA" in white and green typography on a transparent background.
From initial consulting to seamless implementation, we manage your network automation journey every step of the way. Our comprehensive suite of professional services caters to diverse enterprises, ranging from startups to established players.
Contact
1-234-1234
info@netodata.io
Address
Nové sady 988/2
602 00, Brno
Czech Republic
ICO: 23213035
GET IN TOUCH
Address
Netodata Labs, s.r.o. © 2026 All Rights Reserved
Nautobot icon

Nautobot

The central Source of Truth for network infrastructure data. Nautobot serves as:
Authoritative inventory database
IP address components tracking
Configuration template repository
Automation platform

Nornir

A Python automation framework specifically designed for network automation. Nornir provides:
High-performance concurrent task execution
Deep Python integration
Flexible inventory management
Fine-grained control over network operations
CI/CD

Orchestration & CD/CI

We integrate industry-standard orchestration tools to ensure reliable automation delivery:
Git-based version control
Automated pipelines
Controlled deployment workflows
Continuous integration practices

Ansible

An industry-standard automation platform that excels at network configuration management. We utilize Ansible for:
Network device configuration deployment
State validation and compliance checking
Integration with custom Python modules
Standardized workflow automation

Netbox

The central Source of Truth for network infrastructure data. NetBox serves as:
Authoritative inventory database
IP address components tracking
Configuration template repository
REST API provider for automation workflows

Python

The foundation of our automation framework, Python enables us to create modular, maintainable, and efficient network automation solutions. We leverage Python's extensive standard library and carefully selected packages to build:
Reusable automation components
Custom network management tools
API integrations
Data processing pipelines