How to quickly remove multiple entries from the SSH known_hosts file?

I use the SSH protocol to log into multiple hosts several times daily. This creates a lot of entries in my ~/.ssh/known_hosts file.

It often gets cluttered with conflicting entries, for example, when I have moved the host's FQDN to another host (delete an old server, add a new one).

When that happens, I get an error like so:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

This is an expected issue for my use case, which I will explain briefly. But mainly, I will show you how I created a utility you can use in the z-Shell fzf to parse, select and delete entries quickly in this post.

First, a quick intro.

What is the SSH known_hosts File?

The SSH (Secure Shell) known_hosts file is essential to secure communication when connecting to remote servers or devices using SSH protocol. It serves as a repository of trusted host keys, allowing SSH clients to verify the authenticity of the remote server before establishing a connection. This file helps protect against man-in-the-middle attacks, where an attacker intercepts the communication between the client and server and poses as the legitimate server.

When an SSH client connects to a remote server for the first time, the server's host key fingerprint is presented to the client. The client can then choose to accept or reject the key. If accepted, the host key is stored in the known_hosts file, associating the key with the server's hostname or IP address. On subsequent connections, the client checks the known_hosts file to ensure the presented host key matches the one previously stored, thus providing a level of trust and security.

The known_hosts file typically resides in the user's home directory under the .ssh subdirectory. Each entry in the file consists of the server's hostname or IP address, followed by its corresponding host key. The host key serves as a unique identifier for the server, and any discrepancies or changes to the key can raise warnings or errors during SSH connections, indicating a potential security risk.

It is essential to manage the known_hosts file carefully. If a server's host key changes unexpectedly, it may indicate a security breach or configuration change on the remote server. In such cases, it is advisable to investigate the reason behind the change and manually update the known_hosts file to reflect the new key.

Should you delete the entry?

Let me elaborate on my use case to help you understand why I might want to delete an entry from the `known_hosts` file.

When I see an error like so:

Depending on what I am doing, I may have deleted a server and added a new one in its place with the same FQDN example.com to connect to it over SSH.

However, someone might be attacking me if I have not replaced a server. That's where the strict host key-checking policy comes to play.

When deleting the offending entry from the file, you should ask this: Did I change something that would require me to delete the host key?

How to delete one key by hand?

To delete only any key, you can use the ssh-keygen a utility like so:

ssh-keygen -R someserver.example.com

OR

if it is an IP address like 123.123.123.123 you can use this:

ssh-keygen -R 123.123.123.123

You would usually pick the `Host` value from the error I mentioned previously. The -R flag will remove the host key and display a success message like so:

❯ ssh-keygen -R 123.123.123.123
# Host 123.123.123.123 found: line 381
# Host 123.123.123.123 found: line 382
# Host 123.123.123.123 found: line 383
/Users/mrugesh/.ssh/known_hosts updated.
Original contents retained as /Users/mrugesh/.ssh/known_hosts.old

If you look closely, it will back up the known_hosts file to known_hosts.old file in case you want to undo the changes.

Use a ZSH widget?

Yes — of course.

Here is a ZSH utility function that can create a fancy widget to "search", "select" and "delete" multiple entries from the known_hostsfile using the same ssh-keygen tool.

You can include the code below in your ~/.zshrc file or somewhere convenient. I will break down the code in a moment.

    function remove_known_host() {
        # Extract the hostnames from the known_hosts file
        local hostnames=($(awk '{print $1}' ~/.ssh/known_hosts | sed 's/,/\n/g' | sort | uniq))

        # Use 'fzf' to create an interactive menu to select hostnames
        local selected_hostnames=($(printf '%s\n' "${hostnames[@]}" | fzf --multi --prompt="Remove Host > " --query "$LBUFFER"))

        # If any selections were made, remove them
        if [[ ${#selected_hostnames[@]} -ne 0 ]]; then
            for sel in "${selected_hostnames[@]}"; do
                # Remove host entry using ssh-keygen
                ssh-keygen -R "$sel"
            done
            echo "Removed selected host(s)."
        else
            echo "No host selected."
        fi

        # rm -f ~/.ssh/known_hosts.old

        zle reset-prompt
    }

    # Create a widget
    zle -N remove_known_host

    # Optionaly bind a shortcut like Ctrl + H key to invoke the widget
    bindkey '^H' remove_known_host

Here is what the code does:

  1. It defines a function called remove_known_host.

  2. The function parses the known_hosts file to generate a list of hosts, sorted and parsed for uniqueness. This is because you may have multiple Host key entries for the same host (if you have multiple SSH keys, for example).

  3. Next, it uses fzf to create a fancy fuzzy finder widget where you can start typing part of the host you are looking for.

  4. The widget is created in the multi-select mode, so you can navigate through the list using your arrow keys, hit the Tab key to make selections, and repeat as you need.

  5. Once done, you can hit the Enter key to pass the list of entries back to the function.

  6. The code then loops over the entries you selected and uses the ssh-keygen tool to remove the offending host keys.

  7. Uncomment the rm -f ~/.ssh/known_hosts.old line if you do not want to keep the backup file.

  8. The bindkey directive is also optional. You can press Ctrl + H to bring up the widget in the terminal quickly. Comment it out if you don't want the behavior. In that case, you must type in the function name remove_known_host to trigger the widget.

That's it!

You can now source the file or restart your terminal to get it going.

I hope you liked this quick guide. Until the next one!

Did you find this article valuable?

Support Mrugesh Mohapatra by becoming a sponsor. Any amount is appreciated!