AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

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 NETCONF Connection Plugin: Network Configuration Protocol Complete Guide

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.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home