termal.in

← Blog

How to read ssh -vvv output: the debug lines that actually matter

· Termalin team sshtroubleshootingdebuggingtutorial

When SSH fails, the error on the last line is usually a summary, not a diagnosis — Permission denied (publickey) tells you the outcome of a negotiation, not where it went wrong. The actual story is in ssh -vvv, which is why people end up pasting raw fragments like debug3: receive packet: type 51 into a search engine. You don’t need to memorize the protocol to read that trace. You need to know the handful of lines that answer real questions, and which ones are noise.

This is a reading guide: what each -v level adds, the phases you’ll see in order, and a decoder for the lines worth stopping on. (For the errors themselves — refused, timed out, host key warnings — the fixes live in the decoder ring.)

What -v, -vv and -vvv actually add

  • -v prints the debug1 lines: the high-level narrative. Which config files were read, where it’s connecting, what the server offered, which keys were tried, which auth method won. For most problems this is enough.
  • -vv adds debug2: the negotiation details — full algorithm lists from both sides, per-key decision making.
  • -vvv adds debug3: the packet-level trace, including the send packet: type N / receive packet: type N lines. This is the level that names protocol message numbers, and it’s where the searchable mystery lines come from.

Start with -v. Reach for -vvv when the -v story ends abruptly and you need to see the exact packet where it died.

The phases, in the order you’ll see them

Every successful connection walks the same path, and the debug output follows it. Knowing the phases means you can tell where a failure happened before you understand what failed.

1. Config. The first lines are Reading configuration data /home/you/.ssh/config and .../etc/ssh/ssh_config, followed by Applying options for <pattern> for each Host block that matched. If SSH is using a port, user or key you didn’t expect, the culprit is named right here — some block you forgot about is applying. (What belongs in that file, and how matching works: the ~/.ssh/config guide.)

2. TCP connect. Connecting to host [1.2.3.4] port 22. then Connection established. If instead you see connect to address ... Connection refused or Connection timed out, stop reading — nothing after this point matters, because there is no “after”. See below.

3. Version exchange. Local version string SSH-2.0-OpenSSH_... and Remote protocol version 2.0, remote software version ... — the two ends introduce themselves in plaintext. Useful mostly for spotting ancient servers and confirming you reached an sshd at all.

4. Key exchange and host key. SSH2_MSG_KEXINIT sent / received, then the chosen algorithms (kex: algorithm: ..., kex: host key algorithm: ...), then the server’s host key is checked against your known_hosts: Host 'x' is known and matches the ED25519 host key / Found key in ~/.ssh/known_hosts:12. A mismatch aborts here with the big warning banner.

5. Authentication. Authentications that can continue: publickey,password is the server listing what it will accept. Then the client works through methods and keys, one attempt at a time, until something succeeds — or nothing does. This phase is where most reading happens, so it gets its own decoder.

The decoder: lines that answer real questions

Will attempt key: / Offering public key: ... — the answer to “which keys is it trying, and in what order?” At -v you’ll see each candidate: file path or agent entry, key type, fingerprint. If the key you meant isn’t in this list, the server never saw it — no amount of server-side fixing will help. Point at it explicitly with -i and check the config phase for an IdentityFile line that’s overriding you.

debug3: receive packet: type 51 — this is SSH_MSG_USERAUTH_FAILURE: the server rejected that authentication attempt. It is not fatal by itself — the client shrugs and moves to the next key or method, so you’ll often see several type 51s in a healthy login. It becomes the diagnosis only when it follows the key you expected to work: then the server looked at that key and said no — wrong user, key missing from authorized_keys, or bad server-side permissions. That triage is the Permission denied guide. (For orientation: type 50 is the request going out, type 52 is success.)

Server accepts key: followed by failure anyway — the interesting one. The server has confirmed this public key is in authorized_keys and invited the client to prove ownership; if authentication still fails after this line, the problem is on your side of the proof: the agent couldn’t sign (wrong agent socket, agent doesn’t actually hold that private key), a passphrase prompt failed, or the private key on disk doesn’t match the public key that was offered. Look at the sign_and_send_pubkey: line — or its absence — right after.

No more authentication methods to try. — the end state. Every method the server allowed has been exhausted, and the very next line is Permission denied. Don’t read this line as the error; read upward from it to find the last real attempt and why it failed.

A hang at expecting SSH2_MSG_KEX_ECDH_REPLY — not an auth problem at all. The server’s key-exchange reply is the first big packet of the connection, and if it never arrives while everything before it worked, the classic cause is an MTU problem or a misbehaving middlebox — VPNs and some corporate firewalls fragment or drop large packets. Test from another network or without the VPN; if it connects, tune the MTU rather than staring at keys.

Unspecified GSS failure — Kerberos noise. The client tried GSSAPI auth, you’re not in a Kerberos realm, it failed, and it fell back to the next method. If the connection ultimately succeeds or fails on keys, this line played no part in it — ignore it unless you actually use Kerberos SSO.

connect to address ... port 22: Connection refused / Connection timed out — the connection never happened, so every auth question is moot. Refused means the host answered and nothing is listening; timed out means silence on the path. Two different investigations, neither of them involving keys — the two-minute diagnosis covers both branches.

The workflow that finds it fast

Three habits turn a wall of debug text into a two-minute read:

  1. Reproduce with the noise removed. ssh -vvv -o IdentitiesOnly=yes -i ~/.ssh/the_key user@host offers exactly one key, so the trace contains one attempt instead of six, and a type 51 can only mean one thing.
  2. Read bottom-up. Find the failure at the end, then walk upward to the last decision before it — the final Offering public key, the phase boundary it never crossed. The first thirty lines of a trace are almost always healthy.
  3. Check who set what. When behavior doesn’t match your intent, the Reading configuration data and Applying options for lines at the top name the file and block responsible. Half of “SSH is ignoring my flags” is a config block winning quietly.

Fewer traces to read in the first place

The trace is the right tool when something breaks — but a chunk of what it reveals is state you shouldn’t have to rediscover: which key this host wants, which user, which port. Termalin keeps those with the saved host, so the right key is offered on purpose rather than fourth in an agent’s lineup, and verifies each server’s identity on first connect with a loud warning if the fingerprint ever changes — the host-key phase, handled deliberately. Live host status answers “is the box even reachable?” before you connect, which is the refused/timed-out branch of the trace pre-empted entirely. The Free tier has no host limits — download it and save -vvv for the genuinely weird days.


Termalin is a free, cross-platform SSH client that keeps per-host ports, users, keys and keepalives with the host and verifies server identity on every connect — download it, or browse the full feature list.

Try it on one host.

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

Free tier · 14-day Pro trial · pricing