Ansible "UNREACHABLE! Failed to connect to the host via ssh": find the real SSH error
Fix Ansible UNREACHABLE: read the SSH error in the message, check the inventory, reproduce with -vvv and plain ssh, then fix keys, users or host keys.
by OhSudo ··
Your playbook stops before it does anything on a host:
fatal: [web01]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: guest@203.0.113.10: Permission denied (publickey).", "unreachable": true}UNREACHABLE means Ansible couldn’t even open the SSH connection (or couldn’t set up its temporary folder on the host) — no task ran there. Ansible uses plain OpenSSH, so the end of msg is the SSH error itself. Read it first; it usually names the problem.
- Control nodeansible-playbookruns
- OpenSSHkey, user, network, host keyfails to connect(fails)
- Managed hostnever reached(breaks here)
TL;DR
ansible web01 -m ping -vvv # shows the exact ssh command Ansible runsansible-inventory --host web01 # the address, user and key Ansible really usesssh -i ~/.ssh/deploy guest@203.0.113.10 # try the same connection by hand1. Match the SSH error
End of msg |
Meaning | Section |
|---|---|---|
Connection timed out / No route to host |
Nothing answers on that address and port | 2 |
Connection refused |
The host answers, but no SSH server on that port | 2 |
Could not resolve hostname web01 |
The name isn’t in DNS (or /etc/hosts) |
3 |
Permission denied (publickey…) |
Wrong user or key | 4 |
Host key verification failed. |
Unknown or changed host key | 5 |
Failed to create temporary directory |
Connected, but couldn’t write on the host | 6 |
2. Timed out or refused: the network
nc -zv 203.0.113.10 22timed out means a firewall (cloud security group, ufw, firewalld) drops the traffic, the host is down, or the address is wrong. refused means nothing listens on port 22 — SSH may run on another port (ansible_port), or sshd isn’t running. Check the address first with step 3: a stale ansible_host is a classic.
3. Check what Ansible really uses
Inventories get complicated: group variables, host variables, several inventory files. Don’t guess — ask Ansible:
ansible-inventory --host web01{ "ansible_host": "203.0.113.10", "ansible_user": "guest", "ansible_ssh_private_key_file": "~/.ssh/deploy"}No ansible_host? Then Ansible connects to the name (web01), which must resolve in DNS — see Temporary failure in name resolution. No ansible_user? Then it uses your local user name, which often doesn’t exist on the server.
4. Permission denied: user or key
Reproduce it with the exact command Ansible uses. -vvv prints it:
ansible web01 -m ping -vvvLook for the line that starts with SSH: EXEC ssh — it has the user, the port and the key. Run plain ssh with the same values and add -v to see which keys are offered. Usual fixes:
- set
ansible_userto the user that really exists on the host (ubuntu,ec2-user,deploy…); - point
ansible_ssh_private_key_fileto the right key, or load it into your agent (ssh-add ~/.ssh/deploy); - install the public key on the host.
Ansible uses OpenSSH, so user, key and jump host can also live in ~/.ssh/config (inventory variables still win); the SSH Config Generator writes one, and plain ssh web01 then tests exactly what Ansible will do.
When the key is right and still refused, the reason is on the server — see SSH “Permission denied (publickey)”.
5. Host key verification failed
The first time you connect to a host, SSH asks you to confirm its key. Ansible runs without a terminal to answer, so the connection fails. Add the host’s key to known_hosts once:
ssh-keyscan -H 203.0.113.10 >> ~/.ssh/known_hostsIf the key changed (the server was reinstalled, or the IP now belongs to another machine), SSH refuses on purpose. Make sure the change is expected, then remove the old key: ssh-keygen -R 203.0.113.10.
Warning
host_key_checking = False in ansible.cfg makes the error go away by accepting any host key — including one from a machine pretending to be your server. Fine for short-lived lab VMs, not for production.
6. Failed to create temporary directory
"msg": "Failed to create temporary directory. In some cases, you may have been able to authenticate and did not have permissions on the target directory. …"Here SSH worked. Ansible copies its modules to a temporary folder in the user’s home (~/.ansible/tmp) and couldn’t create it. Usual causes: the disk is full (No space left on device), the filesystem is read-only (Read-only file system), or the home folder isn’t writable. You can also point Ansible elsewhere with remote_tmp = /tmp/.ansible-${USER} in ansible.cfg.
Shell, we have a problem!
Similar, but different: FAILED! instead of UNREACHABLE! — the connection worked and a task failed: read that task’s msg. A message like /usr/bin/python3: not found means the host has no Python; install it, or point ansible_python_interpreter to the right one. couldn't resolve module/action means a collection is missing: see that article. Missing sudo password means become: true needs a password: run with -K (--ask-become-pass). If it connects and then stops at sudo, see Missing sudo password. With a password instead of a key, Ansible may first ask for sshpass.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.