SSH with Ansible: Configure Keys and Connection Settings

Ansible doesn’t actually use SSH keys directly; it uses the paramiko or ssh command-line client library to establish connections.

Let’s see Ansible connect to a host using a specific SSH key and some custom connection settings.

---
- name: Connect to host with specific SSH key and settings
  hosts: your_target_host
  gather_facts: no
  tasks:
    - name: Ping the host
      ansible.builtin.ping:

To run this, you’d need an ansible.cfg file or set environment variables. Here’s what ansible.cfg might look like:

[defaults]
inventory = ./inventory.ini
remote_user = your_remote_user
private_key_file = /path/to/your/private_key.pem
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no

And your inventory.ini:

[your_target_host]
192.168.1.100 ansible_ssh_port=22

This setup tells Ansible to use your_remote_user on 192.168.1.100 via port 22, authenticate using the private key at /path/to/your/private_key.pem, and pass specific arguments to the underlying SSH client. The ssh_args are particularly interesting: ControlMaster=auto and ControlPersist=60s enable SSH connection multiplexing, meaning subsequent connections within 60 seconds will reuse the existing, already-authenticated SSH channel, drastically speeding up operations. StrictHostKeyChecking=no bypasses the prompt for unknown host keys, which is common in dynamic environments but a security risk if not managed carefully.

The problem Ansible solves here is the tedious manual configuration of SSH for every host in a managed fleet. Instead of logging into each machine, copying keys, and editing ssh_config, you declare your desired state in Ansible, and it handles the connection details. Internally, when you run an Ansible playbook, the ansible-connection plugin (which defaults to ssh or paramiko for most Linux/Unix targets) constructs the SSH command or uses the paramiko library with the parameters you provide. This includes the remote_user, private_key_file, ssh_args, and any host-specific variables like ansible_ssh_port. It then executes your task (like ansible.builtin.ping) over this established connection.

The private_key_file directive in ansible.cfg is global. If you need to use different keys for different hosts or groups, you’d typically override this in your inventory file or playbook. For example, in inventory.ini:

[webservers]
web1.example.com ansible_user=webadmin ansible_ssh_private_key_file=/etc/ansible/keys/web_key.pem
web2.example.com ansible_user=webadmin ansible_ssh_private_key_file=/etc/ansible/keys/web_key.pem

[dbservers]
db1.example.com ansible_user=dbadmin ansible_ssh_private_key_file=/etc/ansible/keys/db_key.pem

This allows granular control. Notice ansible_ssh_private_key_file is a host variable, taking precedence over the global private_key_file in ansible.cfg.

When Ansible determines the connection parameters for a specific host, it follows a specific order of precedence:

  1. Host variables (e.g., ansible_ssh_private_key_file, ansible_user, ansible_ssh_port defined directly on the host in inventory).
  2. Group variables (defined in group_vars/<group_name>.yml or group_vars/<group_name>.ini).
  3. Play variables (defined in the vars section of a play).
  4. Role variables (defined in vars/main.yml within a role).
  5. ansible.cfg settings (e.g., remote_user, private_key_file, ssh_args).
  6. Environment variables (e.g., ANSIBLE_PRIVATE_KEY_FILE).
  7. Default values built into Ansible.

This layered approach ensures that your most specific configurations always win.

A common point of confusion is when Ansible prompts for a password. This usually means either the private_key_file is incorrect, the permissions on the private key file are too open (SSH requires chmod 600 or 400 for private keys), or the private key itself is passphrase protected and Ansible isn’t configured to handle that. To handle passphrases, you can use ssh-agent or specify ansible_ssh_passphrase (though storing passphrases directly in inventory or playbooks is generally discouraged for security reasons).

The ssh_args parameter is a powerful way to tune SSH behavior beyond just keys and users. For instance, you might set ansible_ssh_common_args to include ProxyCommand for connecting through bastion hosts:

# In ansible.cfg
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
# Or in inventory for a specific host/group
[bastion_hosts]
bastion.example.com

[servers]
server1.example.com ansible_user=appuser ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p user@bastion.example.com"'

This ansible_ssh_common_args variable is appended to the SSH command for that specific host, allowing complex network traversal to be handled transparently by Ansible.

The next step after mastering SSH connections is often managing sudo privileges for tasks that require elevated permissions.

Want structured learning?

Take the full Ssh course →