Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reading traces and diagnosing failures

An SSH trace should reconstruct each peer’s protocol state, message direction, and active security properties. A list of log lines is only source material.

Three views of one connection

flowchart LR
    Capture["Network capture<br/>segments · visible bytes · timing"] --> Model["Protocol reconstruction"]
    Client["Client debug log<br/>local choices · state · errors"] --> Model
    Server["Server debug log<br/>policy · accepted requests"] --> Model

A packet capture shows what crossed an observation point, but after NEWKEYS it normally cannot show message contents. A client log shows what the client believed it sent or received, often after decoding and validation. A server log can reveal why local policy rejected a request. None is a perfect, independent transcript of the whole system.

Debug output may contain hostnames, usernames, paths, fingerprints, commands, and authentication details. Sanitize it before sharing. Peer-supplied banner or error text is untrusted even when it appears in a trusted program’s log.

Annotate the phase first

Start by placing every observation on the connection timeline.

timeline
    title SSH diagnosis boundaries
    Byte stream : address and connection result
    Identification : protocol and software versions
    Negotiation : both proposals and selected algorithms
    Key exchange : shared-secret computation and host proof
    Host trust : destination-to-key decision
    User authentication : methods, factors, success
    Connection protocol : channels, requests, flow control, close

The last completed boundary narrows the failure dramatically. For example, a host-key warning shows that negotiation reached presentation and trust checking of the host key. It does not prove whether signature verification happened first; clients may order those two checks differently.

Keep observations and inferences separate

Suppose a client log says:

kex: algorithm: curve25519-sha256
kex: host key algorithm: ssh-ed25519

This directly reports the client’s selected names. You may infer that each name appeared in both KEXINIT proposals and that it was the first mutually supported value in the client’s corresponding list. To prove that inference from the wire, you would need both complete proposals.

Similarly, seeing ciphertext after NEWKEYS proves that protected packet bytes were exchanged, not that user authentication or a command succeeded.

TCP segments are not SSH packets

A capture tool presents TCP segments according to where it observed the stream. SSH packet boundaries can cross segments, and one segment can contain several SSH packets. Retransmissions are TCP behavior and do not mean SSH processed the same logical bytes twice.

Before binary packets, each direction is line-oriented. After either endpoint sends its identification line, its next byte begins the binary stream in that direction. The two directions can cross this boundary at different times.

Diagnose by boundary

flowchart TD
    Fail["Connection failed"] --> TCP{"Byte stream opened?"}
    TCP -- no --> IO["Address · route · refusal · timeout"]
    TCP -- yes --> ID{"Identification accepted?"}
    ID -- no --> Version["Banner · protocol version · line grammar"]
    ID -- yes --> KEX{"Algorithms selected?"}
    KEX -- no --> Lists["Client policy versus server offers"]
    KEX -- yes --> Host{"Server authenticated?"}
    Host -- no --> Trust["Exchange signature · name · trust binding"]
    Host -- yes --> User{"User authenticated?"}
    User -- no --> Cred["Offered method · credential · server policy"]
    User -- yes --> Chan["Channel · request · window · EOF/CLOSE"]

No matching algorithm

Compare the relevant KEXINIT category, not a single undifferentiated list. A failure might concern the KEX method, host-key algorithm, one direction’s cipher, MAC, or compression. The correct selection is the first client-listed value also present on the server list.

Do not solve the mismatch by blindly enabling every legacy name. Determine whether one side can offer a mutually acceptable current algorithm and why its policy excluded that choice.

Host-key conflict

A changed-host-key failure is not a user-authentication failure. Record the requested destination identity, effective port or host-key alias, route through any jump host, presented fingerprint, and stored trust rule. Verify a legitimate rotation through an independent channel before updating the narrow binding.

User authentication rejected

Track each SSH_MSG_USERAUTH_FAILURE as a pair: its current method list and partial success flag. A valid public-key signature can be accepted as one factor without completing authentication. Method number 60 must be interpreted from the active method state, not from the number alone.

sequenceDiagram
    participant C as Client
    participant S as Server
    C->>S: signed publickey request
    S-->>C: FAILURE(keyboard-interactive, partial=true)
    Note over C,S: Public key factor succeeded<br/>authentication is not complete
    C->>S: keyboard-interactive exchange
    S-->>C: SUCCESS

A channel appears to hang

Check the two channel directions independently. A sender with zero remote window must wait for WINDOW_ADJUST; the other channel direction and other logical channels may continue. Local EOF ends only one data direction. Final output and exit status may legitimately arrive after the client has sent EOF.

flowchart TD
    Stall["No channel data moving"] --> W{"Remote window zero?"}
    W -- yes --> Adj["Look for receiver consumption<br/>and WINDOW_ADJUST"]
    W -- no --> Req{"Start request accepted?"}
    Req -- no --> Setup["OPEN / request success or failure"]
    Req -- yes --> Close{"EOF or CLOSE observed?"}
    Close -- EOF only --> Half["Only one direction ended"]
    Close -- CLOSE both ways --> Done["Channel finished"]

Read disconnects in context

SSH_MSG_DISCONNECT carries a reason code, description, and language tag. The reason is useful evidence, but the description is peer-controlled text and may be vague. Abrupt TCP EOF carries no SSH reason at all. A packet authentication failure may intentionally reveal little because processing unauthenticated data would be unsafe.

The message-number and disconnect-reason registries are collected by IANA, with the original transport behavior in RFC 4253 section 11.

Trace review

For each important line or packet, write down:

  • its direction and protocol layer;
  • the state before and after it;
  • whether its contents were cleartext or protected;
  • which identity or channel it concerned; and
  • whether your conclusion is directly observed or inferred from a protocol rule.

That discipline turns a verbose log into a protocol explanation and prevents a late symptom—such as a closed channel—from being mistaken for the earlier cause.