WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED on Mac: check before you delete known_hosts
Frank Zhang
- 8 minutes read - 3585 wordsTL;DR
The server presented a different host key from the one saved for that host or [host]:port. Name whether the change was expected, compare the SHA256 fingerprint out of band, then remove only that entry.
The server you just reached presented a host key that is not the one saved in ~/.ssh/known_hosts for that host, or for [host]:port when the port is not 22. OpenSSH then stops. The warning is that check succeeding. Deleting the whole file is not the first move.
Match the banner before you edit anything. The wording below is the string in OpenSSH’s sshconnect.c, as read for this page. The fingerprint, the path, the line number, and the key types on your screen are yours. you and example.com here are placeholders.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ 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:…
Please contact your system administrator.
Add correct host key in /Users/you/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /Users/you/.ssh/known_hosts:12
Host key for example.com has changed and you have requested strict checking.
Host key verification failed.
Two fields in that text are easy to treat as one key. They are not. “The fingerprint for the ED25519 key” is the key the server just sent. “Offending ECDSA key in …:12” is the key already stored on line 12. The types can differ. The path in the “Offending” line is the file to inspect. On a Mac that path is the one the client printed, usually under /Users/<name>/.ssh/known_hosts.
The last line, Host key verification failed., is also what you get when the host is new and nobody can answer the yes/no prompt. That case has no banner. Read the lines above the sentence before you decide which case you are in. The section near the end is that second case.
These commands were not re-run on a Mac or a VPS for this page. Flag meanings come from the manuals. The OpenSSH source links are the master tree read while writing. macOS ships its own build of OpenSSH. If man on the machine in front of you disagrees, that copy wins.
Thirty seconds: did you expect a new key?
Both rows still get an out-of-band fingerprint check. The difference is whether you already know why a new key would appear.
| What you can say | Typical situation | Next step |
|---|---|---|
| You can name the change. You rebuilt or reinstalled the VPS, the IP was given to a different machine, frp, cpolar, or another port forward now lands on a different host, or the router or NAS was reset. | A new host key is plausible. | Read the SHA256 line. Compare it with the fingerprint from the provider console, or with the publisher’s page when the host is GitHub. After they match, remove only that known_hosts entry and accept the new prompt only if it shows the same fingerprint. |
| You cannot name a change. You did not reinstall, reassign the address, move the forward, or reset the device. | The warning’s other clause: someone may be intercepting the connection. | Stop. Leave known_hosts as it is. Verify the fingerprint on a channel that is not this SSH connection. If you cannot get that fingerprint, or it does not match, leave the entry in place. |
“I rebuilt it this morning” does not skip the comparison. A rebuild and an interception can share a day. The out-of-band source is the provider’s web console, a keyboard already attached to that machine, a person who can read the host key there, or a fingerprint page the operator publishes. The SSH session that just printed the warning is the path under suspicion.
TCP has already been accepted by the time this banner appears. port 22: Connection refused never reaches a host key. That read-only order is ssh: connect to host port 22: Connection refused, from a Mac or into a Mac.
Read-only checks
Nothing in this section removes a line or writes ~/.ssh/config.
The name the file actually uses
ssh-keygen(1) -F searches a known_hosts file for a hostname, with an optional port written as [hostname]:port. The same paragraph says this search finds hashed names. Quote the brackets so the shell does not treat them as a pathname pattern.
ssh-keygen -F host
ssh-keygen -F '[host]:2222'
Port 22 is stored as the bare host. In the client source read for this page, misc.c put_host_port keeps the bare name when the port is 22, and writes [host]:port otherwise. The comment next to that call in sshconnect.c says the port form exists so a forwarded connection, or a second sshd on the same machine, can have its own key. ssh-keygen -F host will not show the entry that -F '[host]:2222' shows.
-F takes -l in the same manual’s synopsis, which prints the fingerprint of the saved key:
ssh-keygen -lF host
ssh-keygen -lF '[host]:2222'
If the lookup prints nothing, the name you passed is not the name in the file. ssh_config(5) Hostname is the host that actually gets logged into, and Port defaults to 22. A short name in your command can be rewritten before the key is stored. The expanded values, without opening a connection, are:
ssh -G host | grep -iE '^(hostname|port) '
ssh(1) says -G prints the configuration after Host and Match are evaluated, then exits. Run -F again with the hostname and port that command printed. An address you have also connected to is a separate lookup. -F of the name does not list the IP line.
The “Offending … key in …:N” line is still worth opening. Line N is the stored key that failed the comparison. A hashed file will not show the hostname on that line. Finding it is -F’s job, which is why the manual points -F at hashed names.
What is answering on the network, and what the console says
ssh-keyscan(1) collects public host keys. -p is the port. ssh-keygen(1) -l prints the fingerprint of a public key, and -f - reads standard input. In the ssh-keygen source read for this page, a filename of - is stdin, and lines that start with # are skipped, which is the shape of ssh-keyscan’s comment banner.
ssh-keyscan -p 2222 host | ssh-keygen -lf -
Use the port from the error or from ssh -G. For port 22, -p 22 matches. Without -t, the scan can print more than one key type. The fingerprint to line up is the type named in “The fingerprint for the … key sent by the remote host.” -t ed25519 (or ecdsa, or rsa) asks the scan for that type. The manual’s default is to fetch ecdsa, ed25519, the security-key types, and rsa.
That pipeline is the same network path as the SSH connection. The ssh-keyscan manual says the tool cannot verify the keys it obtains, and that an attacker who can intercept the traffic can substitute a key. Treat the output as a second look at what this network is saying. It is not the out-of-band check.
The out-of-band check runs where sshd is, from the provider console or a session that does not depend on the connection you are questioning. ssh(1), under “VERIFYING HOST KEYS”, shows:
ssh-keygen -l -f /etc/ssh/ssh_host_rsa_key
The public-key form for the type named in the warning is the command to use when the server still has the usual OpenSSH filenames. ED25519 in the warning means the ed25519 file:
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
ECDSA and RSA have their own files beside that one. The manual’s example points at the RSA file and omits .pub; -l looks for the matching public key. If this server’s HostKey path is somewhere else, the console command has to use the file that exists. This page did not inventory host-key directories, including the directory used by sshd on a Mac.
Accept a new key only when the fingerprint from the console, or from the publisher’s page, matches both of the others:
- The SHA256 line in the warning (the key just offered).
- The SHA256 line from the console (the key on that machine’s disk).
- After you remove the old entry, the SHA256 line in the “authenticity of host … can’t be established” prompt.
If the prompt shows a different fingerprint from the one you compared, stop and read it again. A match between the warning and the prompt only says the network answered twice with the same key.
Hashed lines do not change this comparison. ssh_config(5) HashKnownHosts hashes names as they are added. The default in that manual is no. A hashed line does not show the hostname, so grep finds nothing. -F and -R still take the plain hostname or [hostname]:port. This page did not read a Mac’s default for HashKnownHosts.
When the host is github.com
github.com in the warning is GitHub’s SSH host key. It is not your deploy key and not ~/.ssh/id_ed25519. Those keys identify you, after the host key has already been accepted. Permission denied (publickey) means the host key check got that far. This banner means it did not.
GitHub publishes the fingerprints to compare, on GitHub’s SSH key fingerprints. Match the SHA256 line in your warning to the row for the type the warning names. The values below are what that page showed on 2026-11-12. If GitHub rotates a key, the page wins and this list does not.
SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s(RSA)SHA256:br9IjFspm1vxR3iA35FWE+4VTyz1hYVLIE2t1/CeyWQ(DSA, labeled on that page as closing down)SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM(ECDSA)SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU(Ed25519)
On 24 March 2023, at about 05:00 UTC, GitHub replaced the RSA SSH host key used for Git operations on GitHub.com. The write-up is We updated our RSA SSH host key (Mike Hanley, dated 23 March 2023). The post says the RSA private key had been briefly exposed in a public repository, that the replacement was precautionary, and that ECDSA and Ed25519 were not changed. Web and HTTPS Git traffic were out of scope there. An old RSA line for github.com produces this warning for a reason GitHub documented. Confirm the warning’s fingerprint against the RSA row above, then remove the github.com entry the same way as any other host. The post’s own removal command is ssh-keygen -R github.com.
Other GitHub hostnames are outside this page. The warning names the host to look up.
Remove that entry only
Do this after the fingerprint matches a source that is not the SSH connection itself.
ssh-keygen(1) -R “removes all keys belonging to the specified hostname (with optional port number) from a known_hosts file.” The same sentence says this is how you delete hashed hosts. One hostname argument removes every key type stored under that name. It does not remove a different name. The IP, and [host]:2222, each need their own -R if they have their own lines.
ssh-keygen -R host
ssh-keygen -R '[host]:2222'
The -R paragraph in the manual does not mention a backup. The ssh-keygen source read for this page, when a deletion happens in place, keeps the previous contents at the same path plus .old, then prints Original contents retained as and that path. On the default user file, the backup sits next to known_hosts. The same function unlinks an existing .old first, so the backup is the file from immediately before this run, one generation deep. Keep it until the new connection has matched. Deleting .old is not part of the fix.
If that source’s “not found” path runs, it logs Host <name> not found in <file> and does not replace the file. The usual reason is a port form on one side and a bare name on the other. Use the form -F already found.
-f selects the file. The default is the user known-hosts file. If the “Offending” line names /etc/ssh/ssh_known_hosts or another path, pass that path with -f. A system file may require sudo. -R without -f leaves the system file alone.
The known_hosts fix command generator on this site fills in -F and -R for the host and port you type. It does not compare fingerprints. The comparison stays with you.
Reconnect after -R:
ssh -p 2222 user@host
With StrictHostKeyChecking at its default, the client asks before saving a new key. ssh_config(5) says ask is the default: new keys are added only after you confirm, and a changed key is refused. The prompt to expect is the first-connection question, “The authenticity of host … can’t be established,” followed by a SHA256 fingerprint and yes/no/[fingerprint]. Type yes only when that fingerprint is the one you already compared. yes writes the key into the user known-hosts file. A fingerprint you have not compared is a key you have not checked.
StrictHostKeyChecking yes never adds a key by itself. The manual’s description of yes is that you add new hosts manually. This page’s reconnect step assumes ask.
Patterns that drop the check
ssh-keygen -R removes the name you passed. Every other host you have already accepted stays in the file. Removing ~/.ssh/known_hosts drops that record, including hosts that had nothing to do with this warning.
StrictHostKeyChecking no and UserKnownHostsFile none, placed where they apply to every host, turn the check off for every host. People also paste UserKnownHostsFile /dev/null. The value in the manual is none: it causes ssh to ignore user-specific known-hosts files. The manual’s description of no (and off) is that new keys are added automatically and connections to hosts with changed keys proceed, “subject to some restrictions.” In the client source read for this page, a changed key with strict checking off is marked untrusted: password and keyboard-interactive authentication are turned off, and agent, X11, port, and tunnel forwarding are turned off. A Host * block, or the same lines above any Host block, applies to GitHub and to every other server. Leave the default in force for those hosts.
A lab machine whose key changes because you rebuild it can have its own block. Host only applies the following lines to the names it matches.
Host lab-box
HostName lab.example
Port 2222
UserKnownHostsFile ~/.ssh/known_hosts.lab
StrictHostKeyChecking accept-new
accept-new, in the same manual, adds a host that is not in the file yet and still refuses a changed key. A rebuild that replaces the key still needs -R against ~/.ssh/known_hosts.lab (or the [host]:port form). The separate file is what keeps that -R out of the known hosts you use for everything else.
The setting that stops refusing a changed key is StrictHostKeyChecking no, inside a block that matches only the lab name. The tradeoff is the one the banner is about: that name is no longer tied to a key you checked, so an interception under that name is not caught by known_hosts. The restrictions in the source above still apply when the presented key does not match a trusted one. UserKnownHostsFile none in that same narrow block means there is no user file to compare for those names. A system known-hosts file, if one exists, is a separate list. This page does not put no or none on a host that carries accounts, GitHub, or anything you cannot afford to have impersonated.
git clone, scp, and CI have nobody to type yes
Host key verification failed. is the fatal line in sshconnect2.c when the host-key check returns failure. git, scp, and a CI job all surface that sentence. scp in current OpenSSH is the same client check. The details of a given git or scp build are general guidance; the fatal line is the source above.
A changed key has no yes/no question. The banner is printed, and unless strict checking is off, the client fails. A terminal and a job log look the same.
A host that is not in known_hosts yet is the other path. The prompt is “Are you sure you want to continue connecting (yes/no/[fingerprint])?”. confirm() in the client source returns immediately, as a no, when BatchMode is set. ssh_config(5) says BatchMode yes disables user interaction such as host key confirmation, and that this is for scripts and batch jobs. With no terminal, the prompt cannot be answered either. git clone, scp in a script, GitHub Actions, and GitLab CI usually have no one sitting there, so a missing entry ends on the same fatal line.
General guidance for those jobs: put a known_hosts line you have already verified where that job’s ssh will read it. For GitHub, the verified source is the fingerprints page above, not a scan performed from the runner. For your own server, it is the fingerprint from the provider console. UserKnownHostsFile can point the job at that file if the job’s home directory is empty. Running ssh-keyscan inside the job collects whatever answers on the runner’s network. The ssh-keyscan manual says that output cannot prove the key is authentic.
GIT_SSH_COMMAND with StrictHostKeyChecking=no is the same tradeoff as the global setting, limited to that one command: the check does not run. A verified known_hosts line is the fix that keeps the check.
GitHub’s 2023 post says Actions runs that use actions/checkout with the ssh-key option can fail across that RSA change, and that they were updating the action’s supported tags. That is GitHub’s note about their rotation. It is not a workflow to copy onto another host. Pinning a key for GitLab CI is the same general guidance: a line from a source you verified, written into the job’s known hosts. This page does not publish a GitLab fingerprint.
Where this page stops
- The commands were not re-run on a Mac or a VPS. The manuals and the source files linked above are the sources. The
manon the machine wins. - This page does not pin a macOS release or Apple’s OpenSSH patch level.
-Rof one name leaves other names. The IP and[host]:portare other names. Several key types under one name go together when you pass that name to-R.HashKnownHostsdefaults tonoin the cited manual. Grep cannot see a hashed name.-Fand-Rcan.ssh-keyscanis not an out-of-band check.CheckHostIPdefaults tonoin the citedssh_config(5). A build that still has it on can printPOSSIBLE DNS SPOOFING DETECTED. That is a different banner. This page does not walk through it.@cert-authority, host certificates, and@revokedare a different mechanism.WARNING: REVOKED HOST KEY DETECTEDis a different banner. This page does not edit those lines by hand.- IPv6 literals are not walked through. When a port is present,
-Fand-Rstill want the[hostname]:portform from the manual. - The Windows OpenSSH client is not this page. The Chinese incident notes that Windows needs different commands.
UpdateHostKeysinssh_config(5)can store additional host keys after a login whose key was already trusted, which is how a planned rotation can be learned. It does not replace the check when the key just offered fails to match.- Search volume. This page does not add an external measurement.
A tunnel whose address and port kept changing is the Chinese incident 一次ssh排错经历. The command generator is known_hosts fix command generator. Other paths on the site start at Start Here. The Chinese writeup of this same topic is Mac 上出现 WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED:先核对指纹,再删 known_hosts 里的那一条.
If this post helps, you can sponsor the blog. This is voluntary personal support, not a charitable donation.