Ansible Error Guide: 'Host key verification failed' SSH Connection Failure
Fix Ansible's 'Failed to connect to the host via ssh: Host key verification failed' error: diagnose stale known_hosts entries, re-imaged hosts, and host key checking.
- #ansible
- #troubleshooting
- #errors
- #ssh
Exact Error Message
fatal: [web-02]: UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: Host key verification failed.",
"unreachable": true
}
You will usually see this on the implicit Gathering Facts step, before any of your real tasks have a chance to run. The play recap shows unreachable=1 for the affected host while other hosts proceed normally.
What the Error Means
SSH verifies the identity of every host it connects to by comparing the server’s public host key against a known, trusted copy stored in ~/.ssh/known_hosts. When Ansible drives SSH and the offered host key does not match the stored entry (or no entry exists and SSH refuses to accept an unknown key non-interactively), SSH aborts with Host key verification failed.
Ansible reports this as UNREACHABLE! rather than a task failure because the transport never completed. With host_key_checking = True (the default), Ansible cannot answer SSH’s interactive “Are you sure you want to continue connecting?” prompt, so an unknown or changed key becomes a hard stop.
Common Causes
- A host was re-imaged or rebuilt and now presents a new host key while keeping the same IP/hostname. The old key in
known_hostsno longer matches. - The host is brand new and was never added to
known_hosts, and Ansible is running non-interactively so it cannot accept the new key. - DNS or a load balancer points the same name at different backend machines with different host keys.
- A man-in-the-middle protection actually firing: someone replaced the host or its key without your knowledge.
known_hostsis being shared from a control image where one operator’s entries do not match another’s environment.
How to Reproduce the Error
Point Ansible at a host whose key has changed. The fastest way to simulate it in a lab is to remove or corrupt the stored entry and connect with strict checking:
ansible web-02 -i inventory.ini -m ping
web-02 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: Host key verification failed.",
"unreachable": true
}
A re-imaged host produces the more alarming variant where SSH believes the key changed:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Diagnostic Commands
Confirm the failure is host-key related and see the raw SSH exchange:
ansible web-02 -i inventory.ini -m ping -vvv 2>&1 | grep -i "host key"
Find out exactly which host/IP Ansible is connecting to:
ansible-inventory -i inventory.ini --host web-02
Inspect what raw SSH does with the same parameters, including the verification logic:
ssh -v -o StrictHostKeyChecking=yes deploy@10.0.4.22 hostname
Show the stored entry for the host (by name and by IP):
ssh-keygen -F web-02
ssh-keygen -F 10.0.4.22
Compare the stored key against what the server is actually presenting now:
ssh-keyscan -t ed25519 10.0.4.22
Step-by-Step Resolution
-
Identify the offending entry. Use
ssh-keygen -Fwith both the hostname and the IP, becauseknown_hostscan store either or both. -
Verify the new key is legitimate. Before trusting a changed key, confirm the host was genuinely rebuilt. Compare
ssh-keyscanoutput against an out-of-band source (cloud console, provisioning logs). Do not blindly accept a changed key on production. -
Remove the stale entry once you trust the new key:
ssh-keygen -R web-02
ssh-keygen -R 10.0.4.22
- Add the correct new key deliberately rather than waiting for an interactive prompt:
ssh-keyscan -t ed25519,rsa 10.0.4.22 >> ~/.ssh/known_hosts
- Re-test with Ansible:
ansible web-02 -i inventory.ini -m ping
web-02 | SUCCESS => {"changed": false, "ping": "pong"}
- For controlled first-run automation against trusted hosts only, you can bypass checking for a single run. Treat this as a deliberate exception, not a default:
ANSIBLE_HOST_KEY_CHECKING=False ansible web-02 -i inventory.ini -m ping
Prevention and Best Practices
- Manage
known_hostsas configuration. Distribute a curatedknown_hostsfrom your provisioning system rather than letting operators accept keys ad hoc. - After re-imaging a host that keeps its IP, make
ssh-keygen -Rpart of the teardown/rebuild runbook so the stale key never lingers. - Prefer
accept-newover disabling checks entirely. Settinghost_key_checking = Truewith SSH’sStrictHostKeyChecking=accept-newtrusts unknown hosts but still rejects changed keys, preserving MITM protection. - Pin host keys for critical hosts and alert when
ssh-keyscanoutput drifts from the stored value. - Avoid
ANSIBLE_HOST_KEY_CHECKING=Falseas a permanent setting; it silently disables a real security control. - Smoke-test connectivity with
ansible all -m pingafter any image, DNS, or network change to catch host-key drift before a real play hits it.
Related Errors
UNREACHABLE! Failed to connect to the host via ssh: Permission denied (publickey)— authentication, not host identity, is rejected.UNREACHABLE! Failed to connect to the host via ssh: Connection refused— sshd is down or the port is wrong.Could not resolve hostname— DNS failure rather than a key mismatch.Warning: Permanently added '...' to the list of known hosts.— informational, not an error; SSH just accepted a new key.
Frequently Asked Questions
Why does this only happen for one host? That host’s key differs from the stored entry — usually because it was rebuilt while peers were not. The mismatch is per-host.
Is disabling host key checking safe? Only for short-lived, trusted, first-contact automation in isolated networks. Permanently disabling it removes protection against a server being silently swapped.
The host was definitely re-imaged — why is removing the entry not enough? Check both the hostname and IP entries. known_hosts can hold separate lines, and hashed entries can hide which one matches. Run ssh-keygen -R against both forms.
Can I scope the fix to one play? Yes. Use ANSIBLE_HOST_KEY_CHECKING=False as an inline environment variable for that single command, or set StrictHostKeyChecking=accept-new in ansible.cfg’s ssh_args to trust new hosts while still rejecting changed keys. For turning noisy SSH failures into a likely root cause quickly, the free incident assistant can help.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.