termal.in

← Blog

10 common SSH errors and how to fix each one

· Termalin team sshtroubleshootingtutorial

SSH error messages are terse on purpose — the protocol won’t tell a stranger why it turned them away. That’s good security and bad ergonomics: the message you see is often two steps removed from the actual problem. This is a field guide to the ten errors you’ll actually meet, what each one really means, and the fastest route to a fix.

One tool before we start: ssh -v user@host (add up to -vvv). Nearly every mystery below stops being a mystery once you see which step of the handshake it dies on.

1. Connection refused

The machine is reachable, but nothing is listening on the port you knocked on. This one is almost never about you:

  • sshd isn’t running — systemctl status sshd from a console (VPS providers have a web console for exactly this moment);
  • it listens on a non-standard port — try ssh -p 2222, or check /etc/ssh/sshd_config for Port;
  • a firewall is actively rejecting (rather than dropping) the port.

If you just rebuilt the server, it’s the firewall. If it worked yesterday, sshd crashed or the port changed.

2. Connection timed out

Different from refused: your packets are vanishing. The host is down, the IP is wrong, or — most often on cloud boxes — a security group / network ACL silently drops port 22. Check the provider’s firewall rules first, then whether you’re on a network that blocks outbound 22 (hotel and office guest Wi-Fi love this; try port 443-based access or a different network to confirm).

3. Permission denied (publickey)

The all-time champion. The server only accepts key auth, and it didn’t accept any key you offered. In order of likelihood:

  • Wrong user. ubuntu on Ubuntu images, ec2-user on Amazon Linux, root on most bare VPSes. The same key with the wrong username produces exactly this error.
  • Your key wasn’t offered. ssh -v shows every key tried. If your key isn’t in the list, point at it explicitly: ssh -i ~/.ssh/id_ed25519 user@host.
  • The key isn’t on the server. Its public half must be one line in ~user/.ssh/authorized_keys — for that user, not root’s.
  • Server-side permissions. sshd refuses to honor authorized_keys if the home dir, ~/.ssh (700), or the file itself (600) is group-writable. This is the classic “I copied the key and it still doesn’t work”.

4. Permission denied when you expected a password prompt

If you wanted password auth and never got asked, the server has PasswordAuthentication no — most cloud images ship that way, and it’s the right default. Use the provider’s console or your existing key to get in, then add the new key properly rather than switching passwords back on.

5. Host key verification failed / REMOTE HOST IDENTIFICATION HAS CHANGED

The scary banner with the ASCII skull energy. The server presented a different key than the one you trusted before. Two explanations: the machine was legitimately rebuilt/reinstalled (overwhelmingly the common case), or something between you and the server is impersonating it (the case the warning exists for).

If — and only if — you know the server was rebuilt: ssh-keygen -R hostname removes the stale entry, and the next connect re-trusts. Don’t blindly script that removal into your workflow; the one time the warning is real, it’s the only warning you get.

6. Too many authentication failures

Your agent enthusiastically offered six keys, the server’s MaxAuthTries said enough. Tell the client to offer only the key you mean:

ssh -o IdentitiesOnly=yes -i ~/.ssh/the_right_key user@host

…or make it permanent in ~/.ssh/config per host. If you carry many keys, IdentitiesOnly yes under Host * will save you this error forever.

7. WARNING: UNPROTECTED PRIVATE KEY FILE

Your private key is readable by others, and the client refuses to use it. chmod 600 ~/.ssh/id_ed25519. On Windows this appears after copying keys between machines or out of a cloud drive — fix it via the file’s Security properties, or keep keys out of synced folders entirely.

8. client_loop: send disconnect: Broken pipe (the session that dies when you look away)

Idle connections dropped by a NAT router or aggressive firewall between you and the server. Keepalives fix it client-side:

Host *
    ServerAliveInterval 30
    ServerAliveCountMax 4

Two minutes of silence tolerated, packets every 30 seconds so the NAT table never forgets you. If sessions die under load rather than idle, that’s a different animal — look at MTU (VPNs especially) rather than keepalives.

9. no matching key exchange method found / no matching cipher

You’re talking to something old — a router, a switch, an appliance stuck on legacy crypto. Modern OpenSSH removed those algorithms for good reason. Re-enable them for that one host only, never globally:

Host ancient-switch
    KexAlgorithms +diffie-hellman-group14-sha1
    Ciphers +aes256-cbc

The error message names the algorithms the server offered; add the least-bad one it lists.

10. ssh_exchange_identification: read: Connection reset by peer

The TCP connection opened and was immediately slammed shut, before SSH even said hello. Usual suspects: fail2ban or DenyHosts has banned your IP (check /etc/hosts.deny and fail2ban’s jail from a console), sshd is overloaded (MaxStartups exhausted by a scanner hammering the port), or a load balancer health check is eating connections. If it’s fail2ban and it’s your own server, the fix is a console login and fail2ban-client unban <your-ip> — then move SSH off scanners’ radar or tighten the jail so you’re not next week’s collateral.

11. Bad configuration option: pubkeyacceptedalgorithms

Not a server problem — your local ssh is too old for your config file. PubkeyAcceptedAlgorithms arrived in OpenSSH 8.5 (it replaced PubkeyAcceptedKeyTypes); feed that line to an older client — stock macOS ssh of a certain age, an old distro, a minimal container image — and it refuses to parse the file at all. Three ways out: upgrade the client (ssh -V tells you where you stand); use the old spelling PubkeyAcceptedKeyTypes, which newer versions still accept as an alias; or guard the line so only capable clients read it:

Match all
    # only OpenSSH >= 8.5 gets here via Include ordering

— in practice, the simplest guard is keeping version-sensitive options in a separate file pulled in with Include, so an old client on the same dotfiles just skips it. The same error shape (Bad configuration option: <something>) always means the same thing: that word is unknown to this binary — check the option’s spelling first, the binary’s version second.

12. did not receive identification string / invalid ssh identification string

Both live at the very first step of SSH — the banner exchange, before any crypto. did not receive identification string from <ip> is logged server-side by sshd when something opened a TCP connection to port 22 but never sent the SSH-2.0-... line: a port scanner, a load-balancer health check, a monitoring probe, or a client that gave up. In your server logs it’s almost always background noise, not a fault — unless it’s your client failing to connect, in which case something on the path (a proxy, a TCP wrapper, an IDS) is eating the banner.

kex_exchange_identification: ... invalid ssh identification string is the mirror image: something did answer, but with non-SSH data. Usual causes, in order: you pointed ssh at the wrong port — an HTTP server, a database, a captive portal returning HTML; a middlebox on the path is intercepting the connection and answering in the server’s place; or a ProxyCommand/ProxyJump misconfiguration is piping something that isn’t an SSH stream into the client — a proxy’s error page arriving where a banner should be. Confirm you’re actually talking to an sshd: nc <host> 22 should print an SSH-2.0-OpenSSH... banner immediately. If it prints HTML or nothing, the port (or the host, or the proxy hop) is wrong, not your key.

13. Unspecified GSS failure / debug1: unspecified GSS failure. Minor code may provide more information

Kerberos noise. OpenSSH attempts GSSAPI (Kerberos) auth before your key by default, and if you’re not on a Kerberos realm — which is most people — it fails and logs this at debug level, then falls back to key auth and connects fine. If the connection ultimately succeeds, this line is harmless; ignore it. It only matters if you actually rely on Kerberos SSO and auth is failing, in which case it’s a real ticket/realm problem (check klist, clock skew, and the host’s SPN in DNS). To stop the noise on a non-Kerberos setup, add GSSAPIAuthentication no to your config.


Make the fixes stick

Half of these are one-off fixes; the other half deserve a line in ~/.ssh/config so they never come back — keepalives, IdentitiesOnly, per-host ports and identities. Config beats memory.

The other thing that beats memory is a client that keeps state for you. Termalin stores per-host settings — port, user, key, keepalives — with the host itself, verifies servers on first connect and warns loudly when a fingerprint changes, and its status bar shows the machine’s vitals live, so “is the box even up?” stops being a diagnostic step. The Free tier has no host limits — grab it and retire a few of these errors permanently.

Try it on one host.

Termalin is a fast SSH client for you — and your agents.

Free tier · 14-day Pro trial · pricing