Production engineering guide for network automation ansible covering patterns, implementation strategies, and operational best practices.
Network automation with Ansible is the practice of modeling, deploying, and maintaining network infrastructure—routers, switches, firewalls, load balancers—using declarative, idempotent playbooks. It matters when network changes must be repeatable, consistent across environments, and versioned with code: during CI/CD pipelines, during infrastructure provisioning, or in response to incidents requiring rapid, multi-device configuration rollouts.
Ansible uses a simple, agentless model: control nodes execute tasks over SSH or API to managed nodes. For network devices, this means no daemons or agents on routers and switches—just an SSH server and a standard OS shell.
Ansible connects to network devices via network inventory plugins. Each device is defined with a host group, and the connection is configured in ansible.cfg.
[defaults]
inventory = ./inventory
remote_user = admin
become = true
become_method = enable
become_user = root
host_key_checking = false
Inventory files use the network group and define device-specific connection parameters.
[all:vars]
ansible_network_os = ios
ansible_ssh_common_args = -o StrictHostKeyChecking=no
[routers]
router1 ansible_host=192.168.1.1 ansible_user=admin
router2 ansible_host=192.168.1.2 ansible_user=admin
[switches]
switch1 ansible_host=192.168.1.10 ansible_user=admin
switch2 ansible_host=192.168.1.11 ansible_user=admin
The ansible_network_os variable determines the connection plugin. For Cisco IOS, it uses network_cli over SSH.
A playbook applies configuration to a group of network devices.
---
- name: Configure network devices
hosts: routers
gather_facts: false
vars:
ip_address: 10.0.0.1
subnet_mask: 255.255.255.0
default_gateway: 10.0.0.254
tasks:
- name: Set hostname
ios_command:
commands:
- hostname {{ inventory_hostname }}
- write memory
register: result
- name: Configure interface
ios_config:
lines:
- interface GigabitEthernet0/0
- ip address {{ ip_address }} {{ subnet_mask }}
- no shutdown
replace: line
register: config_result
- name: Apply configuration
ios_command:
commands:
- show running-config
- show ip interface brief
output: json
register: show_result
- name: Log configuration changes
debug:
msg: "Applied config to {{ inventory_hostname }}"
The ios_command module sends commands directly over the CLI. ios_config applies configuration in bulk and supports replace: line, replace: full, and replace: merge.
Ansible ensures idempotency through the state parameter in modules.
- name: Ensure VLAN 10 exists
ios_vlan:
vlan_id: 10
name: Marketing
state: present
If VLAN 10 already exists, no commands are sent. If it does not, Ansible creates it and applies the name.
The state: absent removes a VLAN:
- name: Remove VLAN 10
ios_vlan:
vlan_id: 10
state: absent
For complex configurations, use Jinja2 templates with ios_config.
- name: Configure OSPF
ios_config:
lines:
- router ospf 1
- network 10.0.0.0 0.0.0.255 area 0
- passive-interface default
- no passive-interface GigabitEthernet0/0
src: templates/ospf.j2
backup: yes
src references a Jinja2 template file.
{# templates/ospf.j2 #}
router ospf {{ ospf_process_id }}
network {{ network_range }} area {{ area_id }}
passive-interface default
no passive-interface GigabitEthernet0/0
{% for intf in passive_interfaces %}
passive-interface {{ intf }}
{% endfor %}
The backup: yes parameter creates a backup of the running config before applying changes.
network_cli for Direct Command ExecutionWhen the ios_config module is insufficient, use ios_command to execute arbitrary CLI commands.
- name: Enable BGP and verify
ios_command:
commands:
- show running-config | section router bgp
- show ip bgp summary
- show ip bgp neighbors
output: json
register: bgp_output
- name: Parse BGP output
debug:
msg: "BGP neighbors: {{ bgp_output.stdout[1] | from_json }}"
The output: json flag returns structured data from the device.
Ansible supports multiple network operating systems via the network_os variable. For Juniper JunOS:
[all:vars]
ansible_network_os = junos
[junos_routers]
junos1 ansible_host=192.168.2.1 ansible_user=admin
- name: Configure Juniper interface
junos_config:
lines:
- set interfaces ge-0/0/0 unit 0 family inet address 192.168.1.1/24
- set interfaces ge-0/0/0 unit 0 description "Upstream"
commit: yes
diff: yes
For Arista EOS:
[all:vars]
ansible_network_os = eos
[eos_switches]
eos1 ansible_host=192.168.3.1 ansible_user=admin
- name: Configure VLANs on EOS
eos_config:
lines:
- vlan 10
- name Marketing
- exit
- vlan 20
- name Engineering
src: configs/vlans.eos
replace: full
The replace: full option replaces the entire configuration, while replace: merge combines new config with existing.
Backups are critical for network change management.
- name: Backup running configuration
ios_config:
save: yes
backup: yes
register: backup_result
- name: Verify backup was created
assert:
that:
- backup_result.backup_file | basename | match("running-config")
fail_msg: "Backup not created in expected format"
The save: yes parameter executes write memory after configuration. backup: yes copies the running config to the control node.
Rollbacks use ios_config with rollback: true.
- name: Rollback configuration
ios_config:
rollback: 1
register: rollback_result
A rollback: 1 returns to the last saved configuration.
Common failure modes include SSH timeouts, command mismatches, and configuration diffs.
When Ansible cannot reach a device:
fatal: [router1]: UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.1.1 port 22: Connection timed out",
"unreachable": true
}
Fix by verifying SSH access and ansible_network_os.
A command may fail with exit status 1, and Ansible logs the output.
- name: Configure interface
ios_command:
commands:
- interface GigabitEthernet0/0
- ip address 192.168.1.100 255.255.255.0
- no shutdown
- ip address 192.168.1.100 255.255.255.0 # duplicate
output: text
register: result
- name: Check for duplicate IP error
assert:
that:
- result.stdout | join('\n') | regex_search("Duplicate address")
fail_msg: "Interface configuration failed due to duplicate IP"
The output: text returns raw CLI output. Use output: json for structured data.
When ios_config detects a difference, it includes diff in the result.
- name: Apply configuration with diff
ios_config:
lines:
- interface GigabitEthernet0/0
- ip address 192.168.1.100 255.255.255.0
- no shutdown
diff: yes
register: config_result
- name: Log configuration diff
debug:
msg: "Config diff: {{ config_result.diff }}"
The diff output shows what was added, removed, or changed.
For multi-environment deployments, use variable files.
# group_vars/routers.yml
ios_config:
backup: yes
commit: yes
replace: full
diff: yes
# group_vars/switches.yml
ios_vlan:
state: present
name: "{{ vlan_name | default('default') }}"
Playbooks can be run with ansible-playbook and --limit, --tags, and --diff.
ansible-playbook site.yml --limit routers --tags "ospf,vlan" --diff
The --diff flag shows changes in configuration files.
For large environments, use dynamic inventory scripts.
# dynamic_inventory.py
#!/usr/bin/env python3
import json
import subprocess
def get_devices():
result = subprocess.run(
["curl", "https://api.example.com/network/devices"],
capture_output=True,
text=True
)
devices = json.loads(result.stdout)
return {
"all": {
"hosts": [d["hostname"] for d in devices],
"vars": {
"ansible_network_os": "ios",
"ansible_user": "admin"
}
},
"routers": {
"hosts": [d["hostname"] for d in devices if d["type"] == "router"]
},
"switches": {
"hosts": [d["hostname"] for d in devices if d["type"] == "switch"]
}
}
if __name__ == "__main__":
print(json.dumps(get_devices()))
Run with:
ansible-inventory -i dynamic_inventory.py --list
ios_config with replace: line may apply the same command multiple times if not idempotent.become: true is essential: Many network devices require enable mode for configuration.gather_facts: false saves time: For large configurations, fact gathering (e.g., setup module) can be skipped.backup: yes is critical: Always back up before a change; use backup: yes with backup_path.rollback: 1 on failure: Use failed_when to trigger rollback:- name: Apply OSPF config
ios_config:
lines:
- router ospf 1
- network 10.0.0.0 0.0.0.255 area 0
backup: yes
register: ospf_result
- name: Rollback on failure
ios_config:
rollback: 1
when: ospf_result.failed
output: json enables parsing: Use from_json and json filters to extract values from CLI output.replace: line vs replace: merge: line replaces only matching lines; merge appends new lines. Confusing them leads to duplicate configuration blocks.commit: yes in junos_config: Without commit: yes, changes are staged but not applied.backup: yes writes to backup/ directory: Ensure the directory is accessible and writable.ansible_network_os not set: Fails silently when the OS is missing or misspelled.become_method: enable missing: Without enable, commands run in exec mode, not config mode.Network automation with Ansible is not just about running commands—it is about modeling the network as code, ensuring consistency, versioning changes, and enabling rapid, reliable deployment across devices.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy