My access switch is a Cisco 2960G. It’s a solid gigabit switch that has been running for the better part of two decades, and I see no reason to replace hardware that works. Modern automation tools disagree. Getting Ansible to manage it turned into a small archaeology project, and since half the enterprise world still runs gear like this, the fixes are worth writing down.
Wall one: SSH won’t even connect
New SSH clients have dropped the key exchange and cipher algorithms old IOS speaks, so the connection dies before login with errors about no matching kex or cipher. The fix is re-enabling the legacy crypto, scoped to just the hosts that need it:
# ansible.cfg
[defaults]
vault_password_file = ~/.ansible_vault_pass
host_key_checking = False # lab only; leave on in prod
[ssh_connection]
ssh_args = -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o Ciphers=+aes256-cbc
You’re explicitly re-enabling algorithms that were retired for a reason, so scope it. If the config holds modern gear too, put the legacy options in an ~/.ssh/config Host block matched to the old devices instead of globally in ansible.cfg.
Wall two: the library underneath
Newer versions of paramiko, the SSH library Ansible leans on for network gear, also dropped legacy support. The connection failures this causes say nothing about versions, which is why it cost me more time than I’d like to admit. The fix is one pin:
pip install 'paramiko<3.0'
With both walls down, a normal network_cli playbook works like the switch was born yesterday:
- name: Baseline the 2960G
hosts: access_switches
gather_facts: no
connection: ansible.netcommon.network_cli
vars:
ansible_network_os: cisco.ios.ios
tasks:
- name: Pull facts
cisco.ios.ios_facts:
gather_subset: all
- name: Save running config for the backup job
cisco.ios.ios_command:
commands: show running-config
register: runcfg
The smaller potholes from the same project
vault_password_file belongs in the [defaults] section of ansible.cfg, not wherever you happen to be when you get tired of typing --ask-vault-pass.
delegate_to: localhost takes your hostvars context with it. If a task needs facts from the switch while running locally, capture them first:
- set_fact:
switch_version: "{{ ansible_net_version }}"
- name: Do something locally with it
delegate_to: localhost
debug:
msg: "Switch runs {{ switch_version }}"
And extra_vars cannot be set in ansible.cfg at all, no matter how much sense that would make. Command line or playbook, nowhere else.
People ask why I bother automating a lab switch when I could type the six commands. Because these gotchas are the same ones waiting in any environment running older gear, and I’d rather meet them at home than during a maintenance window. Old hardware is the best teacher precisely because nothing works out of the box.
