Skip to main content
DevOps Insightsguides & answers

SSH · troubleshooting

Host key verification failed

SSH could not match the server's host key against your known_hosts file, so it stopped before sending anything. Either the key changed, which can be a rebuilt server or someone intercepting the connection, or the host is not in known_hosts and nothing could ask you, as in CI. Verify the new fingerprint against a source you trust before trusting it.

Reproduced on OpenSSH 10.3, . Where the text says “reproduced”, the behaviour was run on that version, not taken from documentation.

First published

New to this? Read what is actually happening first, then come back to the steps.

On this page · 9 sections

Before you start

  • A trustworthy copy of the server's key fingerprint: the provider's documentation (GitHub, GitLab and Bitbucket publish theirs), your cloud console, or the administrator.
  • Write access to your own ~/.ssh/known_hosts, or to the known_hosts file your CI job uses.
  • For a server you run: a way to read its keys outside SSH, such as the cloud provider's serial console or instance logs.

Run this first

step 1 of 4
ssh -v -o BatchMode=yes -T <user>@<host> 2>&1 | grep -Ei 'known_hosts|host key|fingerprint|Server host key'

Look for The server's key type and fingerprint, and the files SSH read

Shows the key the server offered and which known_hosts files were consulted, without logging in.

Work out which cause you have

A few questions to narrow the list. Every answer ends in a command that confirms or rules the cause out — this cannot see your cluster, so nothing here is a certainty until you have checked.

Narrow it down

0 answered · nothing is sent anywhere

Did SSH print the REMOTE HOST IDENTIFICATION HAS CHANGED banner?

Run the connection once in a terminal, without redirecting its output.

Cause space

3 of 3 still possible

  • The server's host key changedCommon
  • The host is not in known_hosts and nothing could ask youCommon
  • SSH is checking a different file or name than you thinkOccasional

Nothing ruled out yet. Answer the question above and the branches your answer eliminates will strike through here.

Or diagnose it manually

In this order. The first command usually contains the whole answer.

  1. Step 1

    ssh -v -o BatchMode=yes -T <user>@<host> 2>&1 | grep -Ei 'known_hosts|host key|fingerprint|Server host key'

    Shows the key the server offered and which known_hosts files were consulted, without logging in.

    Look for The server's key type and fingerprint, and the files SSH read

  2. Step 2

    ssh-keygen -F <host>

    Separates a changed key, where an entry exists, from an unknown host, where none does.

    Look for An entry means the key changed; no output means the host was never recorded

  3. Step 3

    ssh-keyscan -t ed25519 <host> | ssh-keygen -lf -

    Prints the fingerprint of the key the server presents now, to compare with a trusted published one.

    Look for A fingerprint matching the provider's published list or the administrator's

  4. Step 4

    ssh -G <host> | grep -Ei '^(hostname|port|userknownhostsfile|stricthostkeychecking)'

    Confirms the name, port and files in effect, which is where the third cause hides.

    Look for Values matching the entry you expect to be used

Every cause, and how to fix it

Ordered by how often each one turns out to be the answer.

The server's host key changed

Common

known_hosts holds a key for this host and the server now presents a different one. A server rebuilt or replaced under the same name or address, a cloud instance that got a new IP previously used by another machine, or a provider rotating its keys all do this. So would someone intercepting the connection, which is why SSH refuses rather than decides.

Confirm

ssh-keygen -F <host>

An existing entry for the host. Compare the fingerprint in the warning banner with the one the provider or administrator publishes

Fix

  • Confirm the change is legitimate: compare the fingerprint in the warning with the provider's published fingerprints or with what the administrator reads on the server itself.
  • Only then remove the stale entry with ssh-keygen -R <host>. It keeps a backup as known_hosts.old.
  • Connect again and accept the key at the prompt, checking it matches the fingerprint you confirmed, or add the verified line yourself.
  • If the fingerprint does not match anything you can confirm, stop and treat it as a possible interception. Do not connect from that network.
ssh-keyscan -t ed25519 <host> | ssh-keygen -lf -
ssh-keygen -R <host>
ssh-keyscan -t ed25519 <host> >> ~/.ssh/known_hosts

Run the third line only after the fingerprint printed by the first matches a published or out-of-band one. Reproduced on OpenSSH 10.3 against github.com: the scanned fingerprint matched GitHub's published Ed25519 fingerprint, and the connection then passed host verification.

The host is not in known_hosts and nothing could ask you

Common

A first connection normally asks whether to trust the key. In CI, cron jobs, Ansible runs, Docker builds and anything else with BatchMode or no terminal, there is no one to ask, and with StrictHostKeyChecking=yes SSH refuses without asking. Either way the output is only Host key verification failed.

Confirm

ssh-keygen -F <host> -f <known_hosts file the job uses>

No output: the job's known_hosts has no entry for this host. Check the file the job actually uses, which in CI is often not your own

Fix

  • Get the host's fingerprint from a trusted source and add the matching known_hosts line to the job, for example from a CI secret or a file in the repository.
  • For a host you add often, StrictHostKeyChecking accept-new records first-seen keys automatically but still refuses changed ones. It trusts the first connection, so use it only where that risk is acceptable.
  • Do not use StrictHostKeyChecking=no in pipelines. It also accepts changed keys, which is the protection you want most in automation.
mkdir -p ~/.ssh
printf '%s\n' "$KNOWN_HOSTS_LINE" >> ~/.ssh/known_hosts
ssh -o BatchMode=yes -T git@<host>

KNOWN_HOSTS_LINE is the verified line stored as a CI secret or variable. GitHub, GitLab and Bitbucket publish their keys and fingerprints.

SSH is checking a different file or name than you think

Occasional

known_hosts entries are per name and per port. Connecting by IP instead of hostname, through a different port, as another user whose home has its own ~/.ssh, under sudo, or inside a container all look in a different place or for a different key than the one you added.

Confirm

ssh -G <host> | grep -Ei '^(hostname|port|userknownhostsfile|stricthostkeychecking|hashknownhosts)'

The real host name, port and known_hosts files this connection uses. ssh -G prints the configuration it would apply without connecting

Fix

  • Add the key for the exact name and port used. Non-standard ports are stored as [host]:port.
  • Run the check as the same user, and in the same container or environment, that makes the failing connection.

Understanding it properly

Skip this if you are mid-incident — the working part of the page is above. Worth reading afterwards, because understanding the mechanism is what stops the next one.

In plain terms

Every SSH server has a host key, and your machine remembers which key belongs to which server in a file called known_hosts. This error means the key did not check out: either the server presented a different key from the one remembered, or there was no remembered key and SSH was not allowed to ask you. SSH refuses to connect, because a wrong key can mean you are talking to an impostor.

A changed key, and a host that was never recorded

Reproduced with OpenSSH 10.3p1 on macOS against github.com, using throwaway known_hosts files and -F /dev/null so no personal configuration applied. In the first case the file held a made-up ed25519 key for github.com; in the second it was empty and BatchMode was on, as in a CI job. Paths are shown as <dir>.

Command

ssh -o UserKnownHostsFile=<dir>/known_hosts -o BatchMode=yes -T git@github.com

Output

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    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 ED25519 key sent by the remote host is
SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
Please contact your system administrator.
Offending ED25519 key in <dir>/known_hosts:1
Host key for github.com has changed and you have requested strict checking.
Host key verification failed.

$ ssh -o UserKnownHostsFile=<dir>/empty -o BatchMode=yes -T git@github.com
Host key verification failed.

How to read it

  • The banner and Offending ED25519 key in <dir>/known_hosts:1 name the exact file and line holding the key that did not match.
  • The fingerprint the server sent, SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU, is the Ed25519 fingerprint GitHub publishes. Here the server was genuine and the stored key was wrong. Checking against the published list is how you tell which.
  • With an empty known_hosts and BatchMode, the only output was Host key verification failed. and ssh exited with status 255. With StrictHostKeyChecking=yes it added No ED25519 host key is known for github.com and you have requested strict checking.
  • Through Git the same failure is followed by fatal: Could not read from remote repository. and a hint about access rights, which points people at their own key when the problem is the server's.
  • After removing the bad entry with ssh-keygen -R and adding the verified key, the connection got past host verification and stopped at Permission denied (publickey), the next check, as expected with no client key offered.

Words used on this page

Host key
The server's own key pair. It proves the server's identity to you, in the same way your key proves yours to it.
known_hosts
The file, ~/.ssh/known_hosts by default, where SSH records which host key belongs to which host name or address.
Fingerprint
A short hash of a key, such as SHA256:+DiY3…, used to compare keys by eye or against a published list.
StrictHostKeyChecking
The ssh_config option that decides what happens with unknown or changed keys: ask (the default), yes, accept-new, or no.
BatchMode
An ssh option that turns off every prompt. Scripts and CI use it, so a prompt that would have asked you becomes an error instead.
Man-in-the-middle
An attacker between you and the server who relays the connection while reading or changing it. A changed host key is how SSH notices one.
What is actually happening

SSH authenticates the server before you authenticate to it. The server proves it holds the private host key, and your client compares the public half with what known_hosts recorded for that name. Nothing you type, and none of your keys, reach the server until that check passes.

There are two failures behind the one message. A mismatch, where known_hosts has a key for the host and the server sent a different one, gets the large warning banner, because that is what an interception looks like. A missing entry is normally a prompt asking whether to trust the key; with no one to ask, because BatchMode is on or StrictHostKeyChecking is yes, the prompt becomes this error.

StrictHostKeyChecking has four settings, per ssh_config(5). ask, the default, adds new keys only after you confirm and refuses changed ones. yes never adds keys automatically and refuses changed ones. accept-new adds new keys automatically but still refuses changed ones. no adds new keys and lets connections to changed keys proceed. Only no gives up the protection this error provides.

So the fix is never to make SSH stop checking. It is to get the right key into known_hosts: replace a stale one after confirming the change was legitimate, or record a new host's key after confirming its fingerprint.

How to tell this is your problem
WhereWhat you see
ssh or git outputHost key verification failed.
ssh outputWARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! with an Offending key line
ssh output with StrictHostKeyChecking=yesNo ED25519 host key is known for <host> and you have requested strict checking.
git over SSHfatal: Could not read from remote repository. directly after Host key verification failed.
Exit status255 from ssh

How to know it is actually fixed

  • ssh -o BatchMode=yes -T <user>@<host> no longer prints Host key verification failed. It succeeds, or fails at a later step such as Permission denied (publickey).
  • ssh-keygen -F <host> shows exactly one entry, whose fingerprint matches the trusted one.
  • The CI job or script that failed passes without any StrictHostKeyChecking=no override.

Stopping it happening again

  • Keep verified known_hosts entries for the hosts automation talks to, versioned or stored as secrets, instead of scanning keys at run time.
  • When you rebuild a server, plan the host key change: keep its host keys if your process allows it, or publish the new fingerprints before anyone connects.
  • Use accept-new rather than no where first-use trust is acceptable; it still refuses changed keys.
  • Bookmark your Git host's published fingerprints, so a key change can be checked in seconds.

Security considerations

  • Never answer this by setting StrictHostKeyChecking=no or pointing UserKnownHostsFile at /dev/null. Both connect to whoever answers, impostor included, and the documentation describes no as allowing connections to hosts with changed keys.
  • A changed key is worth an explanation every time. Rebuilt servers and provider key rotations are the common reasons, but SSH cannot tell them apart from interception, which is why it stops.
  • Take the expected fingerprint from somewhere other than the connection being checked. ssh-keyscan fetches whatever the network gives you, so its output proves nothing until you compare it with a published or out-of-band fingerprint.
  • In CI, commit or store the verified known_hosts line and use it, rather than running ssh-keyscan at the start of every job. A scan per job trusts the network every time.

Sources

Behaviour described here is drawn from official documentation. Where a figure could not be confirmed on an official page it is attributed in the text rather than stated as canonical.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it