The Problem
You’ve just provisioned a fresh batch of 10 servers and you're ready to deploy your code. You fire off your Ansible playbook, but instead of seeing green 'success' bars, you're hit with a wall of red text. The connection is refused before Ansible even starts its work.
This isn't a broken network or a down server. It is a fundamental security feature of SSH. When you connect to a machine for the first time, SSH checks its unique fingerprint against your local ~/.ssh/known_hosts file. If the host is brand new, or if you've reinstalled the OS and the fingerprint changed, SSH stops and asks: "Are you sure you want to continue connecting?"
Ansible is designed for automation, not conversation. Because it runs non-interactively, it can't type "yes" for you. It simply gives up and throws a fatal error.
Exact Error Message
fatal: [192.168.1.100]: UNREACHABLE! => {
"changed": false,
"msg": "Host key verification failed.",
"unreachable": true
}
Why this happens
Usually, you'll run into this in one of three scenarios:
- **The Fresh Start:** You are connecting to a new VPS or local VM for the very first time.
- **The Rebuild:** You wiped a server (like an AWS EC2 instance) and gave it a fresh OS. The IP stayed the same, but the SSH key changed.
- **IP Recycling:** In dynamic clouds, you might get assigned an IP address that previously belonged to a different server in your `known_hosts`.
Step-by-Step Fixes
Method 1: Disabling Host Key Checking (The Fast Way)
If you are working in a secure lab or a private internal network, you might want to skip the fingerprint check entirely. This is often the go-to fix for temporary CI/CD runners where security is handled at the network level.
To do this, create or edit the ansible.cfg file in your project folder:
[defaults]
host_key_checking = False
Need a one-time fix without editing files? Set an environment variable before running your command:
export ANSIBLE_HOST_KEY_CHECKING=False
ansible-playbook site.yml
Warning: This makes you vulnerable to Man-in-the-Middle (MITM) attacks. Only use this if you trust your network completely.
Method 2: Using ssh-keyscan (The Secure Way)
For production environments, keep verification enabled and simply provide Ansible with the keys it needs. You can "pre-scan" your servers and dump their fingerprints into your local trust store.
# Scan a single server
ssh-keyscan -H 192.168.1.100 >> ~/.ssh/known_hosts
# Scan an entire list of IPs from a file
ssh-keyscan -H -f inventory_ips.txt >> ~/.ssh/known_hosts
The -H flag is helpful here. It hashes the hostnames so that if someone ever sees your known_hosts file, they can't easily map out your entire infrastructure.
Method 3: Purging Stale Keys
If you see a "Host key mismatch" error, it means your computer remembers an old key for that IP. You have to evict the old entry before the new one can be saved. Use ssh-keygen to target the specific IP address:
ssh-keygen -f "~/.ssh/known_hosts" -R "192.168.1.100"
Once the old key is gone, use the ssh-keyscan command from Method 2 to grab the new one.
Method 4: Targeted Inventory Overrides
Sometimes you only want to disable checking for one specific group of servers. You can pass SSH arguments directly inside your inventory file (e.g., hosts.ini).
[dev_servers]
192.168.1.100 ansible_ssh_extra_args='-o StrictHostKeyChecking=no'
Verification
Let's make sure it works. Run the Ansible ping module against your host. If you see a pong, the handshake is successful and you are ready to automate.
ansible all -m ping -i inventory.ini
Expected output:
192.168.1.100 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
Pro-Tips for Production
- **Scope your Config:** Avoid setting `host_key_checking = False` in the global `/etc/ansible/ansible.cfg`. Keep it local to your project to limit security risks.
- **Automate your Pipelines:** In GitLab CI or GitHub Actions, add `ssh-keyscan` to your `before_script`. This lets your pipeline connect to new infrastructure without manual intervention.
- **Bastion Hosts:** If you're using a Jump host, remember that the key check happens on your local control node. If you use `ProxyCommand`, you may need to manage keys for both the gateway and the final destination.

