Network Automation with Ansible: Managing Cisco IOS at Scale
If you’ve ever spent a Friday evening manually pushing configuration changes to 50 Cisco routers, you already understand why network automation exists. One typo, one missed device, one inconsistent ACL entry — and suddenly you’re troubleshooting an outage instead of going home. Ansible has become one of the most widely adopted tools in the NetDevOps movement, and for good reason: it’s agentless, human-readable, and purpose-built for exactly this kind of work.
This post walks through how to use Ansible for managing Cisco IOS devices at scale — from setting up your environment to writing reusable playbooks that actually hold up in production.
Why Ansible Makes Sense for Network Teams

Before diving into configurations and YAML, it’s worth understanding what makes Ansible a natural fit for network automation compared to alternatives like Python scripts or commercial orchestration platforms.
Agentless Architecture
Unlike Puppet or Chef, Ansible doesn’t require any agent software running on your devices. For Cisco IOS devices, this matters enormously — you’re not installing anything on the router. Ansible connects over SSH (or NETCONF) and issues commands directly, just like a human operator would, but faster and without mistakes.
Human-Readable Playbooks
Ansible playbooks are written in YAML, which means a network engineer who has never written code before can read a playbook and understand what it does. That lowers the barrier for adoption across your team, not just among the engineers who love Python.
Idempotency
When you run the same Ansible playbook twice, it won’t make duplicate changes. If the desired state already exists on the device, Ansible skips that task. This principle of idempotency is foundational to safe automation — you can run your playbooks as many times as needed without fear of compounding errors.
Setting Up Your Ansible Environment
Installation and Dependencies
Getting started with Ansible for Cisco IOS requires a few components beyond a standard Ansible installation.
pip install ansible
pip install ansible-pylibssh # Recommended SSH library for networking
ansible-galaxy collection install cisco.ios
The cisco.ios collection provides all the modules you need for interacting with IOS and IOS-XE devices. Always install collections from Ansible Galaxy rather than relying on the built-in ios_* modules, which are increasingly deprecated in favor of the collection-based equivalents.
Inventory File
Your inventory file tells Ansible which devices to manage and how to connect to them. For a network environment, a well-organized inventory is critical.
# inventory/hosts.ini
[core_routers]
router01 ansible_host=10.0.1.1
router02 ansible_host=10.0.1.2
router03 ansible_host=10.0.1.3
[access_switches]
switch01 ansible_host=10.0.2.1
switch02 ansible_host=10.0.2.2
[cisco_ios:children]
core_routers
access_switches
[cisco_ios:vars]
ansible_network_os=cisco.ios.ios
ansible_connection=network_cli
ansible_user=netadmin
ansible_password={{ vault_password }}
ansible_become=yes
ansible_become_method=enable
ansible_become_password={{ vault_enable_password }}
Notice the use of {{ vault_password }} — credentials should always be stored in Ansible Vault, never in plain text. You can encrypt your credentials with:
ansible-vault encrypt_string 'YourSecretPassword' --name 'vault_password'
Core Ansible Modules for Cisco IOS
The cisco.ios collection includes a rich set of modules. Here are the ones you’ll reach for most often.
| Module | Purpose |
|---|---|
cisco.ios.ios_command |
Run arbitrary show commands |
cisco.ios.ios_config |
Push configuration changes |
cisco.ios.ios_facts |
Gather device facts automatically |
cisco.ios.ios_vlans |
Manage VLANs declaratively |
cisco.ios.ios_interfaces |
Configure interface settings |
cisco.ios.ios_bgp_global |
Manage BGP configuration |
The distinction between ios_command and ios_config is important. Use ios_command for read-only operations like gathering show output. Use ios_config for making changes to the running configuration. Mixing them up leads to unexpected behavior.
Writing Your First Production Playbook
Gathering Device Facts
Start simple. Before you automate changes, get comfortable pulling data from your devices.
---
- name: Gather Cisco IOS Facts
hosts: cisco_ios
gather_facts: false
tasks:
- name: Collect device facts
cisco.ios.ios_facts:
gather_subset:
- hardware
- interfaces
- config
- name: Display hostname and IOS version
debug:
msg: "{{ inventory_hostname }} is running IOS version {{ ansible_net_version }}"
Running this playbook gives you an immediate, real-time inventory of every device’s software version, serial number, and interface status — data that would take hours to collect manually.
Applying Configuration at Scale
Here’s a more practical example: standardizing NTP and DNS configuration across your entire fleet.
---
- name: Standardize NTP and DNS on all Cisco IOS devices
hosts: cisco_ios
gather_facts: false
vars:
ntp_servers:
- 10.0.0.10
- 10.0.0.11
dns_servers:
- 8.8.8.8
- 8.8.4.4
dns_domain: corp.example.com
tasks:
- name: Configure NTP servers
cisco.ios.ios_config:
lines:
- "ntp server {{ item }}"
loop: "{{ ntp_servers }}"
- name: Configure DNS settings
cisco.ios.ios_config:
lines:
- "ip domain-name {{ dns_domain }}"
- "ip name-server {{ dns_servers | join(' ') }}"
- name: Save running config to startup config
cisco.ios.ios_config:
save_when: modified
The save_when: modified option is a safety net — it only writes copy run start when the playbook actually made a change, preventing unnecessary writes to NVRAM.
Using Roles for Scalable Playbook Organization
As your automation library grows, flat playbooks become unmanageable. Ansible roles let you package tasks, variables, templates, and handlers into reusable units.
Creating a Role Structure
roles/
cisco_baseline/
tasks/
main.yml
templates/
banner.j2
interface.j2
vars/
main.yml
handlers/
main.yml
defaults/
main.yml
Jinja2 Templates for IOS Configuration
One of the most powerful combinations in NetDevOps is Ansible’s Jinja2 templating engine paired with IOS configuration. You can generate device-specific configurations from a single template.
A banner template (roles/cisco_baseline/templates/banner.j2) might look like this:
banner motd ^
=========================================
Device: {{ inventory_hostname }}
Location: {{ device_location }}
Managed by: Network Automation Team
Unauthorized access is prohibited.
=========================================
^
And the task that deploys it:
- name: Apply login banner
cisco.ios.ios_config:
lines:
- "{{ lookup('template', 'banner.j2') }}"
This scales elegantly — 200 devices all get the correct, consistent banner with the right hostname and location information populated automatically.
Handling Errors and Building Safety Nets
Automation at scale introduces risk. A poorly written playbook that loops over 200 devices can cause an outage in seconds. Build these safety mechanisms into your workflows.
Use Check Mode First
Always test playbooks in check mode before running against production:
ansible-playbook site.yml --check --diff -i inventory/
The --diff flag shows exactly what configuration changes would be made on each device, line by line.
Limit Blast Radius with serial
Control how many devices Ansible touches at once using the serial keyword:
- name: Roll out BGP changes
hosts: core_routers
serial: 1 # Process one device at a time
gather_facts: false
For critical changes, serial: 1 is your best friend. You can also use percentages: serial: "10%" processes 10% of your inventory at a time, giving you time to catch problems before they propagate.
Validate Before Saving
Add a verification task after configuration changes to confirm the expected state before saving:
- name: Verify BGP neighbor is established
cisco.ios.ios_command:
commands:
- show bgp summary
register: bgp_output
- name: Fail if BGP neighbor is not established
fail:
msg: "BGP neighbor check failed. Aborting save."
when: "'Established' not in bgp_output.stdout[0]"
- name: Save configuration
cisco.ios.ios_config:
save_when: modified
Integrating with CI/CD Pipelines
The final step toward true NetDevOps maturity is treating your network configurations like application code. Store your playbooks and role files in Git, use pull requests for change review, and run automated testing pipelines before anything touches production.
A basic GitLab CI pipeline for network automation might:
- Lint playbooks with
ansible-linton every commit - Syntax check with
ansible-playbook --syntax-check - Run in check mode against a staging inventory
- Require approval before running against production
- Log all outputs to a centralized ITSM or audit system
This workflow transforms network change management from a manual, risky process into a peer-reviewed, auditable, and repeatable pipeline.
Conclusion
Ansible has fundamentally changed what’s possible for teams managing Cisco IOS environments at scale. What once required a team of engineers working weekend maintenance windows can now be handled by a tested, version-controlled playbook running in a CI pipeline. The combination of agentless architecture, readable YAML syntax, and the rich cisco.ios collection makes it the go-to choice for teams beginning their network automation journey.
Start small — automate one repetitive task, like NTP standardization or banner deployment. Build confidence in the tooling, establish safe practices like check mode and serial execution, and gradually expand into more complex workflows. The path from manual CLI work to full NetDevOps doesn’t happen overnight, but with Ansible, each step forward delivers immediate, measurable value.


