Ansible for Cisco: Network Automation with IOS, NX-OS, and ASA
By Luca Berton · Published 2024-01-01 · Category: installation
Automate Cisco network devices with Ansible. Configure IOS routers and switches, NX-OS data center switches, ASA firewalls.
Why Ansible for Cisco Network Automation?
Network engineers manage hundreds or thousands of Cisco devices — routers, switches, firewalls, wireless controllers. Manual CLI configuration is slow, error-prone, and doesn't scale. A single misconfigured ACL can take down production traffic.
Ansible automates Cisco devices without installing agents. It connects via SSH (network_cli) or API (httpapi for NX-OS) and executes changes idempotently. One playbook can configure VLANs across 500 switches in minutes.
See also: AAP 2.6 Network Automation: Cisco, Arista, Juniper, and Multi-Vendor Management
Collections
# Cisco IOS (routers, switches)
ansible-galaxy collection install cisco.ios
# Cisco NX-OS (Nexus data center switches)
ansible-galaxy collection install cisco.nxos
# Cisco ASA (firewalls)
ansible-galaxy collection install cisco.asa
# Cisco Meraki (cloud-managed)
ansible-galaxy collection install cisco.meraki
# Cisco ACI (Application Centric Infrastructure)
ansible-galaxy collection install cisco.aci
# Cisco ISE (Identity Services Engine)
ansible-galaxy collection install cisco.ise
Inventory for Network Devices
# inventory/network.yml
all:
children:
ios_devices:
hosts:
core-sw-01:
ansible_host: 10.0.0.1
core-sw-02:
ansible_host: 10.0.0.2
dist-sw-01:
ansible_host: 10.0.1.1
dist-sw-02:
ansible_host: 10.0.1.2
edge-rtr-01:
ansible_host: 10.0.0.254
vars:
ansible_network_os: cisco.ios.ios
ansible_connection: ansible.netcommon.network_cli
ansible_user: ansible
ansible_password: "{{ vault_cisco_password }}"
ansible_become: true
ansible_become_method: enable
ansible_become_password: "{{ vault_enable_password }}"
nxos_devices:
hosts:
dc-spine-01:
ansible_host: 10.0.10.1
dc-spine-02:
ansible_host: 10.0.10.2
dc-leaf-01:
ansible_host: 10.0.10.11
vars:
ansible_network_os: cisco.nxos.nxos
ansible_connection: ansible.netcommon.network_cli
ansible_user: admin
ansible_password: "{{ vault_nxos_password }}"
asa_firewalls:
hosts:
fw-01:
ansible_host: 10.0.0.250
fw-02:
ansible_host: 10.0.0.251
vars:
ansible_network_os: cisco.asa.asa
ansible_connection: ansible.netcommon.network_cli
ansible_user: admin
ansible_password: "{{ vault_asa_password }}"
ansible_become: true
ansible_become_method: enable
See also: Using the AAP 2.6 Self-Service Portal for Network Automation
Configuration Backup
---
- name: Backup all network device configurations
hosts: ios_devices
gather_facts: false
tasks:
- name: Backup running configuration
cisco.ios.ios_config:
backup: true
backup_options:
filename: "{{ inventory_hostname }}_{{ ansible_date_time.date }}.cfg"
dir_path: /backups/network/
register: backup_result
- name: Save running config to startup
cisco.ios.ios_config:
save_when: modified
Automated Daily Backups
---
- name: Daily network backup with diff detection
hosts: ios_devices:nxos_devices
gather_facts: false
tasks:
- name: Get running config
cisco.ios.ios_command:
commands:
- show running-config
register: running_config
when: ansible_network_os == "cisco.ios.ios"
- name: Get NX-OS running config
cisco.nxos.nxos_command:
commands:
- show running-config
register: running_config
when: ansible_network_os == "cisco.nxos.nxos"
- name: Save configuration to file
ansible.builtin.copy:
content: "{{ running_config.stdout[0] }}"
dest: "/backups/{{ inventory_hostname }}/{{ ansible_date_time.date }}.cfg"
delegate_to: localhost
- name: Check for config drift
ansible.builtin.command:
cmd: diff /backups/{{ inventory_hostname }}/baseline.cfg /backups/{{ inventory_hostname }}/{{ ansible_date_time.date }}.cfg
register: config_diff
delegate_to: localhost
changed_when: config_diff.rc != 0
failed_when: false
- name: Alert on config drift
ansible.builtin.debug:
msg: "⚠️ Configuration drift detected on {{ inventory_hostname }}"
when: config_diff.rc != 0
VLAN Management
---
- name: Configure VLANs across all switches
hosts: ios_devices
gather_facts: false
vars:
vlans:
- vlan_id: 10
name: MANAGEMENT
- vlan_id: 20
name: SERVERS
- vlan_id: 30
name: USERS
- vlan_id: 40
name: VOIP
- vlan_id: 50
name: GUEST
- vlan_id: 99
name: NATIVE
tasks:
- name: Configure VLANs
cisco.ios.ios_vlans:
config: "{{ vlans }}"
state: merged
- name: Configure trunk ports
cisco.ios.ios_l2_interfaces:
config:
- name: GigabitEthernet0/1
mode: trunk
trunk:
allowed_vlans: "10,20,30,40,50"
native_vlan: 99
- name: GigabitEthernet0/2
mode: trunk
trunk:
allowed_vlans: "10,20,30,40,50"
native_vlan: 99
state: merged
- name: Configure access ports
cisco.ios.ios_l2_interfaces:
config:
- name: "GigabitEthernet1/{{ item }}"
mode: access
access:
vlan: 30
state: merged
loop: "{{ range(1, 25) | list }}"
See also: Ansible Network Automation: Configure Cisco, Arista, and Juniper at Scale
Interface Configuration
---
- name: Configure interfaces
hosts: ios_devices
gather_facts: false
tasks:
- name: Configure L3 interfaces
cisco.ios.ios_l3_interfaces:
config:
- name: Loopback0
ipv4:
- address: "10.255.0.{{ host_id }}/32"
- name: Vlan10
ipv4:
- address: "10.0.10.{{ host_id }}/24"
state: merged
- name: Configure interface descriptions
cisco.ios.ios_interfaces:
config:
- name: GigabitEthernet0/0
description: "UPLINK to core-sw-01"
enabled: true
speed: "1000"
duplex: full
- name: GigabitEthernet0/1
description: "TRUNK to dist-sw-01"
enabled: true
state: merged
ACL Management
---
- name: Manage ACLs
hosts: ios_devices
gather_facts: false
tasks:
- name: Configure extended ACL
cisco.ios.ios_acls:
config:
- afi: ipv4
acls:
- name: SERVERS_IN
aces:
- sequence: 10
grant: permit
protocol: tcp
source:
address: 10.0.30.0
wildcard_bits: 0.0.0.255
destination:
address: 10.0.20.0
wildcard_bits: 0.0.0.255
destination_port:
eq: 443
log: true
- sequence: 20
grant: permit
protocol: tcp
source:
address: 10.0.30.0
wildcard_bits: 0.0.0.255
destination:
address: 10.0.20.0
wildcard_bits: 0.0.0.255
destination_port:
eq: 22
- sequence: 100
grant: deny
protocol: ip
source:
any: true
destination:
any: true
log: true
state: merged
- name: Apply ACL to interface
cisco.ios.ios_config:
lines:
- ip access-group SERVERS_IN in
parents: interface Vlan30
OSPF Routing
---
- name: Configure OSPF
hosts: ios_devices
gather_facts: false
tasks:
- name: Configure OSPF process
cisco.ios.ios_ospfv2:
config:
processes:
- process_id: 1
router_id: "10.255.0.{{ host_id }}"
areas:
- area_id: "0"
ranges:
- address: 10.0.0.0
netmask: 255.255.0.0
passive_interfaces:
default: true
networks:
- address: 10.0.0.0
wildcard_bits: 0.0.255.255
area: "0"
state: merged
- name: Enable OSPF on non-passive interfaces
cisco.ios.ios_config:
lines:
- no passive-interface GigabitEthernet0/0
- no passive-interface GigabitEthernet0/1
parents: router ospf 1
NX-OS Data Center Configuration
---
- name: Configure Nexus switches
hosts: nxos_devices
gather_facts: false
tasks:
- name: Enable features
cisco.nxos.nxos_feature:
feature: "{{ item }}"
state: enabled
loop:
- vpc
- lacp
- interface-vlan
- hsrp
- ospf
- bgp
- nv overlay
- vn-segment-vlan-based
- name: Configure VLANs
cisco.nxos.nxos_vlans:
config:
- vlan_id: 100
name: WEB_SERVERS
mapped_vni: 10100
- vlan_id: 200
name: APP_SERVERS
mapped_vni: 10200
- vlan_id: 300
name: DB_SERVERS
mapped_vni: 10300
state: merged
- name: Configure vPC
cisco.nxos.nxos_vpc:
domain: 1
role_priority: "{{ vpc_role_priority }}"
system_priority: 2000
peer_gw: true
auto_recovery: true
pkl_dest: "{{ vpc_peer_ip }}"
pkl_src: "{{ vpc_local_ip }}"
state: present
ASA Firewall Configuration
---
- name: Configure ASA firewall
hosts: asa_firewalls
gather_facts: false
tasks:
- name: Configure ACLs
cisco.asa.asa_acls:
config:
- acls:
- name: OUTSIDE_IN
aces:
- grant: permit
protocol: tcp
source:
any: true
destination:
host: 10.0.20.10
destination_port:
eq: 443
- grant: permit
protocol: tcp
source:
any: true
destination:
host: 10.0.20.10
destination_port:
eq: 80
- grant: deny
protocol: ip
source:
any: true
destination:
any: true
log: default
state: merged
- name: Configure NAT
cisco.asa.asa_config:
lines:
- object network WEB_SERVER
- host 10.0.20.10
- nat (inside,outside) static 203.0.113.10
Compliance Checking
---
- name: Network compliance audit
hosts: ios_devices
gather_facts: false
tasks:
- name: Check NTP configuration
cisco.ios.ios_command:
commands:
- show ntp status
register: ntp_status
- name: Verify NTP is synchronized
ansible.builtin.assert:
that:
- "'synchronized' in ntp_status.stdout[0]"
fail_msg: "NTP not synchronized on {{ inventory_hostname }}"
success_msg: "NTP OK"
- name: Check SSH version
cisco.ios.ios_command:
commands:
- show ip ssh
register: ssh_info
- name: Verify SSH v2
ansible.builtin.assert:
that:
- "'SSH Enabled - version 2.0' in ssh_info.stdout[0]"
fail_msg: "SSH v2 not enabled on {{ inventory_hostname }}"
- name: Check for insecure protocols
cisco.ios.ios_command:
commands:
- show running-config | include telnet|http server
register: insecure_check
- name: Flag insecure protocols
ansible.builtin.debug:
msg: "⚠️ Insecure protocol found: {{ insecure_check.stdout_lines }}"
when: insecure_check.stdout[0] | length > 0
- name: Verify banner exists
cisco.ios.ios_command:
commands:
- show running-config | section banner
register: banner_check
- name: Generate compliance report
ansible.builtin.template:
src: compliance-report.j2
dest: "/reports/{{ inventory_hostname }}_compliance_{{ ansible_date_time.date }}.txt"
delegate_to: localhost
FAQ
How does Ansible connect to Cisco devices?
Ansible uses ansible.netcommon.network_cli (SSH) for IOS, NX-OS, and ASA devices. NX-OS also supports ansible.netcommon.httpapi for REST API access. No agent is installed on the network device — Ansible sends CLI commands over SSH just like a human would.
Can Ansible roll back network changes?
Use cisco.ios.ios_config with backup: true before changes, then restore from backup if needed. For NX-OS, use checkpoints: cisco.nxos.nxos_config with checkpoint_file. Best practice: always save a backup before applying changes with state: replaced or state: overridden.
Is Ansible safe for production network changes?
Use --check --diff mode to preview changes without applying them. Use serial: 1 for rolling updates across switches. Implement a "canary" pattern: apply to one device, verify, then roll out to the rest. Back up configs before every change.
What about Cisco DNA Center and Meraki?
The cisco.dnac collection automates Cisco DNA Center (now Catalyst Center) — provisions, monitors, and manages campus networks. The cisco.meraki collection manages cloud-managed Meraki devices via their API. Both work well with Ansible for hybrid environments.
Conclusion
Ansible automates every layer of Cisco networking — IOS routers and switches, NX-OS data center fabrics, ASA firewalls, and cloud-managed Meraki devices. Start with configuration backups and compliance checks (lowest risk), then move to VLAN management, interface configuration, and routing protocols. The resource module approach (ios_vlans, ios_acls, ios_l3_interfaces) provides idempotent, structured configuration management that's safer and more maintainable than raw CLI commands.
Related Articles
• Ansible for IoT and Edge Computing • UFW Allow Port with Ansible • Ansible SIEM SOC Security Operations • AAP 2.6 Automation Mesh • Ansible Dynamic Inventory Complete GuideCategory: installation