Migrating to ssh_type=libssh for ansible.netcommon.network_cli across Ansible Networking Collections

Why libssh

Paramiko is on a long-term deprecation path in the Ansible ecosystem. ansible-pylibssh (libssh) will be the supported SSH backend for network automation going forward, offering better security, maintenance, and feature development. Paramiko has been marked deprecated in ansible.netcommon v8.5.0 and will be removed in a release after 2028-02-01. All network collections should migrate to libssh.

The ssh_type=libssh setting applies to the ansible.netcommon.network_cli connection plugin, which is the primary SSH-based connection used by Ansible network collections.

Prerequisites

Install ansible-pylibssh


pip install ansible-pylibssh>=1.4.0

Verify installation

pip list | grep -i pylibssh

python3 -c "import pylibsshext; print(pylibsshext.__version__)"

Required collection versions

Collection Minimum Version Notes
ansible.netcommon >= 6.0.0 Full libssh support; v8.5.0+ recommended
ansible-pylibssh >= 1.4.0 Required by netcommon v8.5.0+

How ssh_type Works

The ssh_type option controls which Python SSH library the ansible.netcommon.network_cli connection plugin uses to connect to network devices. There are three choices:

Value Behavior
auto Default. Uses libssh if ansible-pylibssh is installed, falls back to paramiko
libssh Explicitly use libssh. Fails if ansible-pylibssh is not installed
paramiko Use paramiko (deprecated, removal after 2028-02-01)

Ways to Set ssh_type=libssh

The following methods configure ssh_type=libssh for the ansible.netcommon.network_cli connection plugin. Pick the one that fits your deployment.

1. Environment variable (global)

export ANSIBLE_NETWORK_CLI_SSH_TYPE=libssh

2. ansible.cfg (project-level)

[persistent_connection]

ssh_type = libssh

3. Inventory variable (per-host or per-group)

[all:vars]

ansible_network_cli_ssh_type=libssh

4. Playbook variable (per-play or per-task)

vars:
  ansible_network_cli_ssh_type: libssh

Collection Examples


Cisco IOS (cisco.ios)

Inventory (inventory_ios.ini)

[ios_devices]
ios-router ansible_host=192.168.1.10

[ios_devices:vars]
ansible_connection=ansible.netcommon.network_cli
ansible_network_os=cisco.ios.ios
ansible_user=admin
ansible_password=secret
ansible_network_cli_ssh_type=libssh

Playbook: Gather facts and push config

---
- name: Cisco IOS - libssh examples
  hosts: ios_devices
  gather_facts: false

  tasks:
    - name: Gather IOS facts
      cisco.ios.ios_facts:
        gather_subset: all

    - name: Show version
      cisco.ios.ios_command:
        commands:
          - show version
          - show ip interface brief
      register: output

    - name: Display output
      ansible.builtin.debug:
        var: output.stdout_lines

    - name: Configure logging
      cisco.ios.ios_config:
        lines:
          - logging buffered 8192
          - logging console informational
        save_when: modified

    - name: Configure interfaces using resource module
      cisco.ios.ios_l3_interfaces:
        config:
          - name: Loopback100
            ipv4:
              - address: 10.100.0.1/32
        state: merged

Run it

ansible-playbook -i inventory_ios.ini ios_libssh_examples.yaml -vvv

File Transfer with libssh (net_put / net_get)

File transfer operations use the SSH transport directly. When using libssh, the underlying SFTP/SCP implementation comes from ansible-pylibssh.

Upload a file to a device

---
- name: File transfer with libssh
  hosts: ios_devices
  gather_facts: false
  vars:
    ansible_network_cli_ssh_type: libssh

  tasks:
    - name: Upload file to device (SCP)
      ansible.netcommon.net_put:
        src: ./configs/running_backup.cfg
        dest: flash:running_backup.cfg
        protocol: scp

    - name: Upload file to device (SFTP)
      ansible.netcommon.net_put:
        src: ./configs/startup_template.cfg
        dest: flash:startup_template.cfg
        protocol: sftp

Download a file from a device

- name: Download file from device
      ansible.netcommon.net_get:
        src: running-config
        dest: ./backups/{{ inventory_hostname }}_backup.cfg
        protocol: scp

Important: File transfer behavior can differ by device, OS image, and protocol (SCP vs SFTP). Test on your platforms after switching from paramiko. Retest whenever you upgrade ansible.netcommon, ansible-pylibssh, or your network collections.


Using ansible.netcommon Modules Directly with libssh

These modules work across all network OSes when ssh_type=libssh is set on the ansible.netcommon.network_cli connection.

---
- name: Generic netcommon modules with libssh
  hosts: all
  gather_facts: false
  vars:
    ansible_network_cli_ssh_type: libssh

  tasks:
    - name: Run arbitrary CLI command
      ansible.netcommon.cli_command:
        command: show version
      register: version_output

    - name: Display version
      ansible.builtin.debug:
        var: version_output.stdout

    - name: Push config lines
      ansible.netcommon.cli_config:
        config: |
          interface Loopback99
           description Configured via libssh

    - name: Network reachability test
      ansible.netcommon.net_ping:
        dest: 8.8.8.8
        count: 3
      register: ping_result
      ignore_errors: true

Migrating Variables: Paramiko / SSH to libssh

When switching from paramiko (or OpenSSH-based connections) to libssh, several connection variables must be updated. libssh uses its own namespace (ansible_libssh_*) and does not inherit paramiko or OpenSSH variables. If you skip this step, your existing authentication, host key, and proxy settings will be silently ignored — connections may fail or fall back to unexpected defaults with no warning.

Variables that MUST change

These variables are not shared across SSH backends. If you used them with paramiko or OpenSSH, you must replace them with the libssh equivalents.

Setting Paramiko / OpenSSH variable libssh variable What changed How to verify
Private key file ansible_ssh_private_key_file or ansible_paramiko_private_key_file ansible_libssh_private_key_file libssh does not read the old variable at all. If only the old variable is set, libssh ignores your key and falls back to password auth or fails. Run a playbook with -vvvv and confirm the log shows Loading private key from <your_key_path>. If you see password authentication instead, the key variable is not being picked up.
Host key checking ansible_host_key_checking or ansible_paramiko_host_key_auto_add ansible_libssh_host_key_checking Paramiko’s host_key_auto_add=true silently accepted and saved unknown host keys to known hosts. libssh’s host_key_checking=false skips verification entirely but does not save keys. In production, set to true and pre-populate your known hosts file. Connect to a device not in ~/.ssh/known_hosts. With host_key_checking=true, the connection should fail. With host_key_checking=false, it should succeed (lab only).
Proxy / jump host ansible_ssh_common_args (e.g., -o ProxyCommand=“ssh -W %h:%p bastion”) ansible_libssh_proxy_command This is the most common migration pitfall. ansible_ssh_common_args is OpenSSH-specific and is completely ignored by libssh. Also, drop the -o ProxyCommand= wrapper — pass the command string directly. Connect to a device behind a bastion host. With -vvvv, confirm the log shows the proxy command being invoked. If the connection goes direct (bypassing the bastion), the old variable is still in use.
SSH config file ~/.ssh/config (auto-loaded by OpenSSH) ansible_libssh_config_file OpenSSH automatically reads ~/.ssh/config for host aliases, identity files, and proxy settings. libssh does not auto-load it — you must point to it explicitly. If you rely on ~/.ssh/config, set ansible_libssh_config_file=~/.ssh/config and verify the connection picks up settings defined there (e.g., a host alias or identity file).
Auto-discover keys ansible_paramiko_look_for_keys No equivalent Paramiko could automatically search ~/.ssh/ for usable private keys. libssh does not do this. You must specify your key explicitly. Unset ansible_libssh_private_key_file and confirm that libssh does not magically find your key. Then set it explicitly and confirm authentication succeeds.

Variables that stay the same (no change needed)

These variables are shared across all SSH backends (paramiko, libssh, OpenSSH). They work as-is after migration.

Setting Variable Notes
SSH password ansible_password or ansible_ssh_pass Used for password-based authentication. Works the same with libssh.
SSH user ansible_user The remote username. Shared across all connection types.
Connection timeout ansible_connect_timeout How long to wait for the initial SSH connection. No change needed.
Command timeout ansible_command_timeout How long to wait for a command to complete on the device. No change needed.
Become / enable ansible_become, ansible_become_method, ansible_become_pass Privilege escalation is handled at the connection plugin level, not the SSH library. No change needed.

Side-by-side example: inventory before and after

Before (paramiko):

[all:vars]
ansible_connection=ansible.netcommon.network_cli
ansible_network_os=cisco.ios.ios
ansible_network_cli_ssh_type=paramiko

# Authentication
ansible_user=admin
ansible_ssh_private_key_file=~/.ssh/id_rsa

# Host key handling
ansible_paramiko_host_key_auto_add=true

# Proxy / bastion
ansible_ssh_common_args=-o ProxyCommand="ssh -W %h:%p bastion.example.com"

# Timeouts (these stay the same)
ansible_connect_timeout=30
ansible_command_timeout=60

After (libssh):

[all:vars]
ansible_connection=ansible.netcommon.network_cli
ansible_network_os=cisco.ios.ios
ansible_network_cli_ssh_type=libssh

# Authentication — variable name changed
ansible_user=admin
ansible_libssh_private_key_file=~/.ssh/id_rsa

#Use true in production (requires pre-populated known hosts); set false only in lab/test       
ansible_libssh_host_key_checking=true 


# Proxy / bastion — variable name changed, syntax simplified (no -o ProxyCommand= wrapper)
ansible_libssh_proxy_command=ssh -W %h:%p bastion.example.com

# Timeouts (unchanged)
ansible_connect_timeout=30
ansible_command_timeout=60

Migration checklist

Use this checklist to verify you have covered all variable changes:

  • Search your inventory files, group_vars/, and host_vars/ for ansible_ssh_private_key_file and ansible_paramiko_private_key_file — replace with ansible_libssh_private_key_file
  • Search for ansible_paramiko_host_key_auto_add and ansible_host_key_checking — replace with ansible_libssh_host_key_checking
  • Search for ansible_ssh_common_args containing ProxyCommand — replace with ansible_libssh_proxy_command (drop the -o ProxyCommand= prefix)
  • Search for ansible_paramiko_look_for_keys — remove it and ensure keys are set explicitly via ansible_libssh_private_key_file
  • If you rely on ~/.ssh/config — add ansible_libssh_config_file=~/.ssh/config
  • Run a test playbook with -vvvv against each platform and verify in the logs that libssh is the active backend and all authentication/proxy settings are applied correctly

What Else You Should Verify

Before declaring your migration to libssh complete, work through these checks for each platform and workflow you support.

1. Environment and dependencies

  • ansible-pylibssh is installed and meets the minimum version: pip show ansible-pylibssh (>= 1.4.0)
  • ansible.netcommon collection meets the minimum version: ansible-galaxy collection list ansible.netcommon (>= 6.0.0, 8.5.0+ recommended)
  • Python can import the library without error: python3 -c “import pylibsshext; print(pylibsshext.version)”

2. Connection and authentication

  • SSH connectivity works with libssh to each target device (password auth)
  • SSH connectivity works with libssh using key-based authentication (ansible_libssh_private_key_file)
  • Host key verification behaves as expected (accept known hosts, reject unknown in production)
  • If using a proxy/jump host: ansible_libssh_proxy_command connects through the bastion successfully
  • Connection timeouts are reasonable for your environment (ansible_command_timeout, ansible_connect_timeout)

3. Core operations per platform

For each platform you use (e.g., Cisco IOS):

  • *_facts module returns facts successfully (e.g., cisco.ios.ios_facts)
  • *_command module executes show commands and returns expected output
  • *_config module pushes configuration changes and they persist on the device
  • Resource modules (*_interfaces, *_vlans, *_acls, etc.) apply config with state: merged and report correct changed status
  • Resource modules with state: replaced and state: deleted behave correctly
  • Idempotency: running the same playbook twice results in changed: false on the second run

4. File transfer (if applicable)

  • net_put successfully uploads files to the device over SCP
  • net_put successfully uploads files to the device over SFTP
  • net_get successfully downloads files from the device
  • File contents match after transfer (no corruption, no truncation)
  • Test with both small files (< 1 KB) and larger files (> 1 MB) if your workflows require it

5. Behavioral differences from paramiko

  • Verify that error messages and failure modes are still actionable in your automation (libssh may surface different error text than paramiko)
  • If your playbooks rely on ansible_ssh_common_args, switch to ansible_libssh_proxy_command – ansible_ssh_common_args does not apply to libssh
  • If you set ansible_paramiko_* variables anywhere, confirm they are replaced with the equivalent ansible_libssh_* or ansible_network_cli_* variables
  • Confirm that long-running commands (large config pushes, file copies) complete without unexpected timeouts

Debugging and Troubleshooting

Enable verbose output


ansible-playbook -i inventory.ini playbook.yaml -vvvv

Enable libssh debug logging


export ANSIBLE_LOG_PATH=./ansible_libssh_debug.log

export ANSIBLE_DEBUG=1

At verbosity -vvvv (level 4+), libssh automatically switches to DEBUG-level logging.

Verify which SSH backend is active

Add this task to confirm libssh is being used:

  - name: Verify SSH backend
      ansible.builtin.debug:
        msg: "SSH type configured: {{ ansible_network_cli_ssh_type | default('auto') }}"

Common issues and fixes

Issue Fix
ModuleNotFoundError: pylibsshext Install: pip install ansible-pylibssh>=1.4.0
Timeout errors on connection Increase ansible_command_timeout in inventory or ansible.cfg
File transfer fails with net_put Try switching protocol from scp to sftp or vice versa
Host key verification failure Set ansible_libssh_host_key_checking=false (lab only)
Proxy command not working Use ansible_libssh_proxy_command (not ansible_ssh_common_args)

Summary

Connection Type Variable to Set Value Notes
network_cli ansible_network_cli_ssh_type libssh Works with all vendor collections
netconf ansible_netconf_libssh true Needs ncclient>=0.7.0
httpapi N/A N/A REST-based, does not use SSH
grpc N/A N/A gRPC-based, does not use SSH

Support

If you encounter any issues while using ssh_type=libssh with the ansible.netcommon.network_cli connection plugin, you can open a GitHub issue on the relevant repository:

2 Likes