Introduction

When Ansible connects to a managed host via SSH, it verifies the server's host key against the local known_hosts file to prevent man-in-the-middle attacks. If the host key doesn't match (because the server was rebuilt, IP was reassigned, or this is the first connection), Ansible marks the host as UNREACHABLE and refuses to proceed. While this security feature protects against MITM attacks, it commonly breaks automation after infrastructure changes.

Understanding the difference between legitimate host key changes (server rebuilds) and potential security issues is critical for resolving these errors safely.

Symptoms

Host key verification failed:

bash
$ ansible web-server -m ping
web-server | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\r\n@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @\r\n@IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!@\r\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\r\nIT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\r\nSomeone could be eavesdropping on you right now (man-in-the-middle attack)!\r\nIt is also possible that a host key has just been changed.\r\nThe fingerprint for the RSA key sent by the remote host is\r\nSHA256:abc123...\r\nPlease contact your system administrator.\r\nAdd correct host key in /home/user/.ssh/known_hosts to get rid of this message.\r\nHost key verification failed.\r\n",
    "unreachable": true
}

Unknown host key:

bash
$ ansible new-server -m ping
new-server | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: Host key verification failed.\r\n",
    "unreachable": true
}

SSH directly shows the issue:

bash
$ ssh web-server
The authenticity of host 'web-server (10.0.1.50)' can't be established.
ECDSA key fingerprint is SHA256:xyz789...
Are you sure you want to continue connecting (yes/no/[fingerprint])?

After server rebuild:

bash
$ ssh rebuilt-server
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:newkey123...
Offending RSA key in /home/user/.ssh/known_hosts:15
RSA host key for rebuilt-server has changed
Host key verification failed.

Checking known_hosts:

bash
$ cat ~/.ssh/known_hosts | grep web-server
web-server ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ...oldkey...
# This key no longer matches the server

Common Causes

1. Server Rebuilt or Reinstalled

New OS installation generates new SSH host keys:

```bash # Before rebuild web-server: ssh-rsa AAAAB3...oldkey...

# After rebuild - server has new key web-server: ssh-rsa AAAAB3...newkey... ```

2. IP Address Reassigned

IP reused by a different server with different host keys:

bash
# IP 10.0.1.50 was server-a
# Now IP 10.0.1.50 is server-b with different keys

3. First Connection to New Host

Host key not yet in known_hosts:

bash
$ grep new-server ~/.ssh/known_hosts
# No entry - first connection

4. SSH Port or Protocol Changed

Server reconfigured with different SSH settings:

bash
# Server changed from RSA to ECDSA
# Or from port 22 to port 2222

5. Load Balancer or Proxy

Connecting through a proxy that presents different keys:

bash
# Direct connection to web-server uses key A
# Connection through bastion uses key B

6. DNS Change

Hostname now resolves to different IP:

bash
# web-server.example.com was 10.0.1.50
# Now resolves to 10.0.1.60 (different host)

Step-by-Step Fix

Step 1: Verify This Is a Legitimate Change

Before removing host keys, verify the change is expected:

```bash # Check if this was a known rebuild # Verify with your infrastructure team or check CI/CD logs

# Test with verbose SSH to see fingerprint ssh -v web-server 2>&1 | grep "fingerprint"

# Compare with expected fingerprint (from your config management) ssh-keyscan web-server | ssh-keygen -lf - ```

Step 2: Remove Old Host Key

Remove the outdated host key entry:

```bash # Remove by hostname ssh-keygen -R web-server

# Remove by IP address ssh-keygen -R 10.0.1.50

# Remove both hostname and IP ssh-keygen -R web-server.example.com ssh-keygen -R web-server ssh-keygen -R 10.0.1.50

# View the change cat ~/.ssh/known_hosts ```

Step 3: Add New Host Key

Add the correct host key:

```bash # Method 1: SSH and accept manually (verify fingerprint first!) ssh web-server # Type 'yes' when prompted

# Method 2: Use ssh-keyscan (automated) ssh-keyscan -H web-server >> ~/.ssh/known_hosts

# Method 3: With specific key type ssh-keyscan -t rsa,ecdsa,ed25519 web-server >> ~/.ssh/known_hosts

# Method 4: With port specified ssh-keyscan -p 2222 web-server >> ~/.ssh/known_hosts ```

Step 4: Verify Connection Works

Test SSH connection:

```bash # Test direct SSH ssh web-server "hostname"

# Test with Ansible ansible web-server -m ping

# Test with verbose output ansible web-server -m ping -vvv ```

Step 5: Automate Host Key Management

Create a playbook for host key management:

```yaml # manage_host_keys.yml - name: Manage SSH host keys hosts: localhost gather_facts: false vars: target_hosts: "{{ groups['all'] }}" known_hosts_file: ~/.ssh/known_hosts

tasks: - name: Get host keys for all targets shell: | ssh-keyscan -H {{ item }} >> {{ known_hosts_file }} 2>/dev/null loop: "{{ target_hosts }}" changed_when: true register: keyscan

  • name: Verify all hosts are reachable
  • ansible.builtin.wait_for:
  • host: "{{ item }}"
  • port: 22
  • timeout: 10
  • loop: "{{ target_hosts }}"
  • delegate_to: localhost
  • run_once: true
  • `

Step 6: Configure Host Key Checking

For controlled environments, you can adjust host key checking:

```ini # ansible.cfg - DO NOT DISABLE IN PRODUCTION [defaults] # Disable host key checking (not recommended for production) host_key_checking = False

# Use SSH arguments to disable per-session [ssh_connection] ssh_args = -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ```

Better approach - use environment-specific config:

```ini # ansible.cfg (production) [defaults] host_key_checking = True

# group_vars/development.yml ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" ```

Step 7: Handle Host Key Changes in CI/CD

Automate host key refresh in pipelines:

```yaml # refresh_host_keys.yml - name: Refresh SSH host keys after infrastructure changes hosts: localhost gather_facts: false vars: infrastructure_hosts: "{{ groups['target'] }}"

tasks: - name: Remove old host keys command: ssh-keygen -R {{ item }} loop: "{{ infrastructure_hosts }}" changed_when: "'updated' in result.stdout" failed_when: false register: remove_result

  • name: Scan new host keys
  • command: ssh-keyscan -H {{ item }}
  • register: new_keys
  • loop: "{{ infrastructure_hosts }}"
  • changed_when: false
  • name: Add new host keys to known_hosts
  • copy:
  • content: "{{ item.stdout }}\n"
  • dest: ~/.ssh/known_hosts
  • mode: '0600'
  • loop: "{{ new_keys.results }}"
  • when: item.stdout | length > 0
  • name: Verify connectivity
  • ansible.builtin.wait_for:
  • host: "{{ item }}"
  • port: 22
  • timeout: 30
  • loop: "{{ infrastructure_hosts }}"
  • `

Step 8: Use Hashed Known Hosts

Use hashed hostnames in known_hosts for security:

```bash # Scan with hashed hostnames ssh-keyscan -H web-server >> ~/.ssh/known_hosts

# The entry will be hashed: # |1|hashvalue|hashvalue ssh-rsa AAAAB3...

# To verify an entry exists for a host ssh-keygen -F web-server ```

Verification

Test SSH connectivity:

```bash # Test with SSH directly ssh web-server "echo 'Connection successful'"

# Test with Ansible ping ansible web-server -m ping

# Expected output: # web-server | SUCCESS => { # "ansible_facts": {...}, # "changed": false, # "ping": "pong" # }

# Test with verbose ansible web-server -m ping -vvv # Should not show host key errors ```

Verify known_hosts state:

```bash # Check host key exists ssh-keygen -F web-server

# Expected output: # # Host web-server found: line 15 # web-server ssh-rsa AAAAB3...

# List all known hosts cat ~/.ssh/known_hosts ```

Run actual playbook:

```bash # Run playbook against target ansible-playbook site.yml --limit web-server

# Verify no UNREACHABLE errors # All tasks should complete or fail for other reasons ```

  • [ansible-ssh-connection-refused-unreachable](/articles/ansible-ssh-connection-refused-unreachable)
  • [ansible-ssh-timeout-issues](/articles/ansible-ssh-timeout-issues)
  • [ansible-security-ssh-configuration](/articles/ansible-security-ssh-configuration)
  • [WordPress troubleshooting: Ansible Artifact Download Uses an Old Mi](ansible-artifact-download-uses-an-old-mirror-after-proxy-change)
  • [WordPress troubleshooting: Ansible Audit Trail Misses Events Under ](ansible-audit-trail-misses-events-under-burst-load)
  • [WordPress troubleshooting: Ansible Background Worker Gets Stuck in ](ansible-background-worker-stuck-in-a-retry-loop)
  • [WordPress troubleshooting: Ansible Backup Completes but Restore Fai](ansible-backup-completes-but-restore-fails-checksum-validation)
  • [WordPress troubleshooting: Ansible Batch Importer Duplicates Rows A](ansible-batch-importer-duplicates-rows-after-a-retry)

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "WordPress troubleshooting: Ansible SSH Host Key Verification Failed", "description": "Learn how to fix Ansible SSH Host Key Verification Failed. Professional WordPress troubleshooting solutions with step-by-step guidance. WP error fix, WordPress optimization, WP security, WordPress performance.", "url": "https://www.fixwikihub.com/ansible-ssh-unreachable-host-key-verification-failed", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-01-22T13:55:20.168Z", "dateModified": "2026-01-22T13:55:20.168Z" } </script>