Automating Cisco, Arista, and Juniper network devices with Ansible: getting to know network collections, network_cli/httpapi/netconf connections, running-config backup, and deploying VLANs and ACLs at scale.

After episode 21, where we covered advanced inventory management and dynamic inventory — from cloud provider plugins to CMDB integration like NetBox and ServiceNow — in this episode we'll point Ansible at one of the most overlooked infrastructure segments in the automation world: network devices.
You might think, "automation is for servers, not for switches and routers." In reality, this is exactly where automation delivers the biggest and most quickly felt impact. Imagine a network engineering team having to add a new VLAN to 40 access switches simultaneously. Manually SSHing into 40 devices, typing the same commands over and over, while hoping none of them has a typo — that could take half a workday, or even longer if there are configuration differences between devices. One typo on device 23 could take a network segment down and bring emergency calls in the middle of the night.
Ansible turns that job into a single playbook run once, spreading across all devices in parallel. Combined with the idempotency concept we've known since the beginning, you can push the same configuration repeatedly without side effects. In this episode we'll cover why Ansible fits network automation, the network collections and modules available, the three connection types (network_cli, httpapi, netconf), and real-world case studies: automatic running-config backup, VLAN and ACL deployment, and templated configuration for many switches at once.
There are strong reasons why Ansible has become the de facto standard for network automation:
ansible_network_os.ios_config only sends commands if the configuration isn't there yet, so there's no config bloat and no unnecessary changes.forks (episode 15) lets Ansible manage dozens to hundreds of devices in parallel.The analogy is simple: if servers are "city residents" that need to be handled one by one, then network devices are the "bridges and highways" connecting them all. Server automation without network automation is like building a new city but still opening and closing bridges manually.
Ansible supports almost all major network operating systems through the platform modules model. Each vendor has its own official collection:
| Platform | Collection | ansible_network_os | Example Modules |
|---|---|---|---|
| Cisco IOS | cisco.ios | ios | ios_config, ios_facts, ios_vlan, ios_acl_interfaces |
| Cisco NX-OS | cisco.nxos | nxos | nxos_config, nxos_facts, nxos_vlan |
| Cisco IOS-XR | cisco.iosxr | iosxr | iosxr_config, iosxr_facts |
| Arista EOS | arista.eos | eos | eos_config, eos_facts, eos_vlans |
| Juniper Junos | junipernetworks.junos | junos | junos_config, junos_facts, junos_interfaces |
| VyOS | community.vyos | vyos | vyos_config, vyos_facts |
| Palo Alto PAN-OS | paloaltonetworks.panos | panos | panos_config, panos_commit |
| F5 BIG-IP | f5networks.f5_bigip | bigip | bigip_virtual_server, bigip_command |
Beyond vendor collections, the ansible.netcommon collection provides common network functionality — helpers, filters, and basic connections used across platforms. This collection is a dependency of almost every network collection, so make sure it's always installed:
ansible-galaxy collection install ansible.netcommon cisco.ios arista.eos junipernetworks.junosNote
The example file names in this article use a collective convention as placeholders. To follow along, adjust to the platform you use: ios_config for Cisco IOS, nxos_config for NX-OS, eos_config for Arista EOS, and junos_config for Juniper Junos.
network_cli, httpapi, and netconfThis is the key concept that distinguishes network automation from server automation. When dealing with network devices, you can't just use a regular ssh connection with the command module — devices have different ways of interacting. Ansible provides three dedicated connection plugins:
| Connection | Protocol | Suitable For | Description |
|---|---|---|---|
network_cli | SSH (CLI over SSH) | Cisco IOS/NX-OS, Arista EOS, VyOS | Most common; Ansible "talks" directly to the CLI like an engineer typing manually |
httpapi | REST API / HTTP(S) | Devices with RESTCONF/API (e.g. Cisco IOS-XE, Palo Alto, F5) | Faster and structured; auth via token or basic |
netconf | NETCONF (XML over SSH) | Juniper Junos, Cisco IOS-XE/NX-OS | Industry standard for model-driven with transactional config support |
The differences between the three can be seen in a single ansible.cfg configuration comparison:
[defaults]
# network_cli: default untuk mayoritas platform
[network_cli:ios]
ansible_connection: network_cli
[network_cli:junos]
ansible_connection: netconf
[device_httpapi]
ansible_connection: httpapi
ansible_httpapi_use_ssl: true
ansible_httpapi_validate_certs: falseUsually the connection is determined at the host/group inventory level, not in ansible.cfg. For example:
all:
children:
core_switches:
hosts:
sw-core-01:
ansible_host: 192.168.1.10
ansible_user: admin
ansible_password: "{{ vault_admin_password }}"
ansible_network_os: ios
ansible_connection: network_cli
ansible_become: true
ansible_become_method: enable
edge_routers:
hosts:
rtr-edge-01:
ansible_host: 192.168.2.1
ansible_network_os: iosxe
ansible_connection: httpapi
ansible_httpapi_use_ssl: true
ansible_httpapi_validate_certs: falseNotice the variables ansible_network_os and ansible_become_method: enable. For most network devices, privileged EXEC mode is accessed with the enable command, not sudo like on Linux. That's why ansible_become_method is set to enable.
Important
The ansible_password variable above shouldn't be written directly. Use Ansible Vault (episode 14) or the credential store on AWX/AAP (episode 20) to store device passwords. Network device credentials are highly sensitive assets — access to a switch means access to the entire network segment.
*_factsJust like servers have facts (ansible_facts), network devices have their own facts collected by modules like ios_facts, nxos_facts, or eos_facts. These facts become the basis for adaptive configuration — for example, knowing the IOS version before deciding which commands are safe to use.
The following playbook gathers facts from all Cisco devices in the all group:
- name: Kumpulkan facts dari seluruh perangkat Cisco
hosts: all
gather_facts: false
tasks:
- name: Ambil facts IOS
cisco.ios.ios_facts:
gather_subset: all
- name: Tampilkan ringkasan facts
ansible.builtin.debug:
msg: >
Hostname: {{ ansible_net_hostname }}
Platform: {{ ansible_net_version }}
Serial: {{ ansible_net_serialnum }}
Model: {{ ansible_net_model }}The concise output appearing per device looks roughly like this:
TASK [Tampilkan ringkasan facts]
ok: [sw-core-01] => {
"msg": "Hostname: sw-core-01 Platform: 15.2(4)E9 Serial: FCW2139G0XW Model: WS-C2960X-48TS"
}
ok: [sw-access-07] => {
"msg": "Hostname: sw-access-07 Platform: 15.2(2)E5 Serial: FOC1750Z2GD Model: WS-C2960+48TC"
}Tip
Use gather_facts: false in network playbooks. Network devices don't run the regular setup module; facts are collected via *_facts modules. Keeping gather_facts: false speeds up execution and avoids unnecessary errors.
ios_configThe *_config module is the heart of network configuration management. This module accepts a list of commands, little by little (called lines), and places them under a parent context. What makes it unique compared to just sending raw commands is the idempotency mechanism: Ansible only sends commands that don't already exist on the device.
A classic example: adding a VLAN to several switches at once.
- name: Deploy VLAN ke seluruh switch akses
hosts: switches
gather_facts: false
tasks:
- name: Buat VLAN 100 dan 200
cisco.ios.ios_config:
lines:
- vlan 100
- name VLAN_DATA
- vlan 200
- name VLAN_VOIP
save_when: modifiedAfter the configuration is successfully changed, save_when: modified writes the config to NVRAM so the changes survive a reboot — equivalent to typing write memory in the CLI.
Managing ACLs is also easy. The following example applies an inbound ACL on the GigabitEthernet0/1 interface to restrict SSH access to only the management network:
- name: Terapkan ACL untuk interface management
hosts: core_switches
gather_facts: false
tasks:
- name: Buat ACL 101
cisco.ios.ios_config:
lines:
- permit tcp host 10.10.10.10 any eq 22
- permit tcp host 10.10.10.11 any eq 22
- deny ip any any log
parents: ip access-list extended MGMT-SSH
- name: Terapkan ACL di interface
cisco.ios.ios_config:
lines:
- ip access-group MGMT-SSH in
parents: interface GigabitEthernet0/1
before: no ip access-group MGMT-SSH in
save_when: modifiedNotice the keyword before: no ip access-group MGMT-SSH in. This prevents the ACL from stacking twice when the command is run repeatedly. The "remove first, then apply" pattern is a very useful idempotency trick for commands that can accept duplicates.
One of the greatest strengths of network automation is templating: a single Jinja2 template file (recall episode 8) filled with different values per device. Imagine you have 40 access switches and each switch has an almost identical trunk VLAN number, only differing in a few details. With a template, you only define it once:
!
interface {{ interface }}
description {{ interface_desc }}
switchport mode trunk
switchport trunk allowed vlan {{ trunk_vlans }}
switchport trunk native vlan {{ native_vlan }}Then the following playbook renders the template and pushes it to each switch, with variable values from each device's host_vars:
- name: Deploy konfigurasi trunk berbasis template
hosts: switches
gather_facts: false
tasks:
- name: Render template dan terapkan ke perangkat
cisco.ios.ios_config:
src: templates/vlan_trunk.j2
save_when: modifiedThe variable values interface, trunk_vlans, and native_vlan come from each switch's host_vars. Result: one playbook, one template, 40 devices with consistent yet unique configuration per need.
Losing a network device's configuration without a backup is a nightmare — a new device that's misconfigured alone can cut the network, let alone having to restore from zero. Ansible can automate backing up each device's running-config to the control node. The *_config module provides the backup parameter, which saves the current configuration to a timestamped file:
- name: Backup running-config seluruh perangkat
hosts: all
gather_facts: false
tasks:
- name: Ambil backup running-config
cisco.ios.ios_config:
backup: true
backup_options:
filename: "{{ ansible_host }}.cfg"
dir_path: backups/Running this playbook produces a backup file structure on the control node:
backups/
├── 2026-08-02T14-30-05/
│ ├── 192.168.1.10.cfg
│ ├── 192.168.1.11.cfg
│ └── 192.168.2.1.cfgTip
Combine automatic backups with scheduling on AWX/AAP (episode 20) or a cron job so running-config snapshots are taken every night without human intervention. A backup that never runs is a backup that doesn't exist. Bonus: commit the backup results to Git so they can be diffed across versions and easily audited.
Routing configuration can also be fully automated. The following example adds static routes and enables OSPF on Cisco devices:
- name: Konfigurasi routing
hosts: edge_routers
gather_facts: false
tasks:
- name: Tambah route statis
cisco.ios.ios_config:
lines:
- ip route 10.20.0.0 255.255.0.0 192.168.2.254
save_when: modified
- name: Aktifkan OSPF di area 0
cisco.ios.ios_config:
lines:
- network 192.168.2.0 0.0.0.255 area 0
- network 192.168.1.0 0.0.0.255 area 0
parents: router ospf 1
save_when: modifiedThe *_config module with parents lets you penetrate multiple context levels at once — exactly like typing configure terminal then router ospf 1 manually, but automatic and safe.
1. Using a regular ssh connection and the command module
This is the most fatal mistake. Network devices don't follow how the ansible.builtin.command module works, which sends a command and reads an exit code. Use the dedicated modules (ios_config, ios_facts, etc.) and the network_cli/httpapi/netconf connections.
2. Forgetting ansible_become_method: enable
On Cisco devices, privileged EXEC commands need the enable command. Without ansible_become: true and ansible_become_method: enable, tasks will fail because commands are rejected by user EXEC mode.
3. Not using save_when: modified
Configuration successfully applied in memory will be lost when the device reboots if it isn't saved. Always use save_when: modified so changes persist to NVRAM (unless that's not desired).
4. Setting the wrong ansible_network_os
Each platform must use the correct ansible_network_os (ios for Cisco IOS, nxos for NX-OS, junos for Juniper, etc.). A wrong value makes Ansible use the wrong module and potentially send commands the device doesn't understand.
5. Overwriting configuration without parents or before
Sending commands without the correct context can accidentally replace an entire interface configuration. Use parents to target context, and before for idempotent operations like duplicate removal.
6. Neglecting backups
Never deploy large configuration without backing up first. Make backup: true a habit, or run a backup playbook as the first step in a network change management pipeline.
In this episode we've covered network automation with Ansible thoroughly: why Ansible is the top choice for network devices, collections and modules for various vendors (Cisco IOS/NX-OS, Arista EOS, Juniper Junos), the three connection types network_cli, httpapi, and netconf, VLAN, ACL, and routing management, template-based configuration deployment to many switches at once, and automatic running-config backup. We also identified common mistakes that often make network playbooks fail.
With this capability, you've completed one more important dimension of infrastructure automation: from Linux servers and cloud to the network devices that form the backbone of data communication. A rare skill that's highly valued in the industry, because engineers who can bridge DevOps and networking are still scarce.
In episode 23, we'll enter the modern application era: Kubernetes & Container Orchestration — managing containers and Kubernetes clusters using the kubernetes.core and community.docker collections, from the k8s module for Deployments and Services, Helm chart deployment, to programmatic Docker container and image management. Keep your enthusiasm up!