The ones you’ll hit most, and what each really means: Connection refused — nothing is listening on that port, or a firewall is in the way. Permission denied (publickey) — the server accepted none of the keys you offered. Host key verification failed — the server’s host key changed since you last trusted it. invalid ssh identification string — the port answered with something that isn’t SSH. Those four, plus nine more, are decoded below with the exact fix.
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 thirteen 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 — reading the ssh -vvv output). Nearly every mystery below stops being a mystery once you see which step of the handshake it dies on.
The errors at a glance
Jump straight to your error, or read the fixes in order below.
| Error | What it means | First move |
|---|---|---|
Connection refused | Host answered; nothing listening on that port | Check sshd is up and on the port you dialed |
Connection timed out | Packets vanished on the path | Check the cloud firewall / security group |
Permission denied (publickey) | No key you offered was accepted | Verify the user, then the key, then authorized_keys |
Permission denied (no prompt) | Password auth is off; a key must work | Use a key, or get in via the provider console |
Host key verification failed | The server’s key changed since you trusted it | Verify out-of-band, then ssh-keygen -R |
Too many authentication failures | The agent offered too many keys | -o IdentitiesOnly=yes with -i |
UNPROTECTED PRIVATE KEY FILE | The key file is readable by others | chmod 600 the private key |
Broken pipe | An idle connection was dropped | Add ServerAliveInterval |
no matching key exchange method | Legacy server, modern client | Re-enable one algorithm for that host only |
Connection reset by peer | TCP slammed shut before the SSH hello | Check fail2ban / MaxStartups |
Bad configuration option | Your client is too old for a config line | Upgrade ssh, or guard the line |
invalid ssh identification string | Non-SSH data on the port, or a proxy in the way | Confirm it’s an sshd with nc host 22 |
Unspecified GSS failure | A Kerberos attempt failed, then fell back | Ignore it if the login still succeeds |
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 sshdfrom 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_configforPort; - 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.
Full fix: connection refused vs timed out, decoded.
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).
Full fix: the same refused-vs-timed-out walkthrough has the timed-out branch step by step.
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.
ubuntuon Ubuntu images,ec2-useron Amazon Linux,rooton most bare VPSes. The same key with the wrong username produces exactly this error — cloud images each pick their own login user, which is half of first-time EC2/VPS login. - Your key wasn’t offered.
ssh -vshows 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_keysif 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”.
Full fix: Permission denied (publickey), a fast systematic fix works every culprit in order.
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.
Full fix: Host key verification failed, and the right way to fix it.
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. An SSH client for Windows that stores the key with the host sidesteps the file-permission dance altogether.
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. kex_exchange_identification / 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.
invalid ssh identification string
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. No terminal handy to run nc? Opening the host in a browser SSH terminal is a quick way to see whether anything SSH-shaped answers at all.
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.