skip to content

A user reports `Permission denied (publickey)` connecting to a Linux server. Walk through what `ssh -vvv deploy@server` tells you about where the failure lies — and what that output fundamentally cannot tell you.

level: seniorimportance: should knowfreq 44%

answer

  1. client half of the conversation
  2. was the key even offered?
  3. offered but skipped means server-side
  4. the client is told nothing on purpose
  5. finish it in the server's log

basics

~20 s

Verbose client output shows which identities the client offered and which methods the server will accept, so it separates a client that never sent your key from a server that rejected it. It cannot show why the server refused — that reason exists only in the server's auth log.

solid answer

~50 s

`-vvv` gives you the client's half of the conversation, and the useful lines are few. `Authentications that can continue: publickey` tells you which methods the server is willing to entertain. `Offering public key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:...` tells you the client actually presented that specific key; `Server accepts key:` means the server matched it and the failure is later. If your key never appears in an `Offering` line, the problem is on your side — wrong identity file, an agent holding the wrong keys, or a file the client refused because its permissions are too open. If it is offered and the server moves on, the problem is on the server: key absent from that account's `authorized_keys`, ownership or modes rejected, a filesystem label the daemon cannot read, or an account that cannot log in at all. The client is never told which, by design, so you finish the diagnosis in `journalctl -u sshd` on the host.

code

bash · 1 line
bash
ssh -vvv deploy@server 2>&1 | grep -E 'Authentications that can continue|Offering public key|Server accepts key|Permission denied'

go deeper

for a junior

Know that -v and -vvv make the client narrate what it is doing, and that the key question to answer from it is whether your key was offered to the server at all.

for a middle

Be able to name the decisive lines — the accepted-methods list, Offering public key, Server accepts key — and explain what each one rules out on the client or server side.

for a senior

Show the two-branch triage and finish it on the host with the daemon's log, articulating that the terse client message is a deliberate anti-enumeration choice rather than a shortcoming.

for a principal

Turn the recurring case into a runbook and a platform fix: consistent key distribution, log fields good enough to attribute a session to a key fingerprint, and access paths that do not depend on one engineer's local agent state.

## Why a structured read of `-vvv` beats scrolling it Verbose output is long and most of it is protocol noise. The value comes from knowing the four or five lines that carry a verdict, and what each one lets you eliminate. ## The lines that matter **Which methods are on the table.** ``` debug1: Authentications that can continue: publickey,keyboard-interactive ``` The server sent this list. If `password` is absent, no amount of typing a password will help — that is a server-side decision, and it reframes the whole problem. This line reappears after each failed attempt, and watching the list shrink shows what has already been consumed. **Which identities the client will try.** ``` debug1: Will attempt key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:abc... agent debug1: Offering public key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:abc... ``` `Will attempt` is the candidate list, drawn from the agent and the default identity files. `Offering` means the key was actually presented to the server. The distinction matters: a key can be in the candidate list and never offered because the attempt limit was reached first. **Whether the server liked it.** ``` debug1: Server accepts key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:abc... ``` This is the good line. It means the server found that public key in the account's authorised list. If it is missing and the next thing you see is another `Offering` or the final refusal, the server declined that key. **The end.** ``` deploy@server: Permission denied (publickey). ``` ## The two branches Everything above resolves into one binary split, and stating it cleanly is the point of the question: 1. **Your key was never offered.** The failure is client-side. Causes: the identity you expected is not loaded and not at a default path; the agent is offering five other keys and the server cut the attempt off after the maximum number of tries; the private key file has permissions the client refuses to use, in which case there is a loud `UNPROTECTED PRIVATE KEY FILE` warning above; or you are connecting as a different user than you think. Retrying with a single explicit identity is the fastest way to isolate this — if naming the key makes it work, an over-full agent was the cause. 2. **Your key was offered and the server moved on.** The failure is server-side, and the client will never learn why. Candidate causes, roughly in order of frequency: the key is not in that account's `~/.ssh/authorized_keys`; the ownership or permissions on the home directory, `~/.ssh`, or the file are rejected; on an SELinux host the directory carries a label the daemon may not read; the account is locked or has a `nologin` shell; the account is outside whatever access list the server enforces. ## Why the client is kept in the dark The silence is deliberate. Distinguishing "no such user", "user exists but key not authorised" and "key authorised but modes rejected" to an unauthenticated caller would leak account existence and configuration to anyone who can reach the port. So the client always gets the same terse refusal, and the detail is written on the server. That is the sentence to say out loud in an interview — it explains why so many people go in circles on the client side. ## Finishing the diagnosis on the server ``` journalctl -u sshd -f ``` then reproduce the login. The daemon logs the account, the source address and, on an acceptance, the key type and `SHA256:` fingerprint — which is how you confirm *which* key was used when several are authorised. Refusals are explicit about the modes case (`Authentication refused: bad ownership or modes for directory ...`) and about invalid users. If you need still more, raising the daemon's log verbosity temporarily produces per-attempt detail; put it back afterwards, because the higher levels are noisy and can record more than you want to keep. A useful trick for the ambiguous middle: compare fingerprints across the two sides. `ssh-keygen -lf` over the client's public key and over the lines in the remote `authorized_keys` will tell you in one step whether the key the client offered is even present on the server — which resolves the most common cause without reading a single log line. ## The connection-level cases this does *not* cover If `-vvv` never reaches the authentication stage — it stalls at `Connecting to ...` or the connection is refused or times out — you are not debugging authentication at all; you are debugging reachability, and the answer lies with the network path and whether the daemon is listening, not with keys.

  • The verbose output shows five keys offered and then a refusal, but the correct key is one of them. What is likely happening?
    The server stopped accepting attempts before reaching the right one — there is a per-connection limit on authentication tries, and an agent loaded with many identities burns through it. Isolate by connecting with a single explicitly named identity; if that succeeds, prune the agent or bind the specific key for that host in your client configuration.
  • How would you confirm from the server side which authorised key a successful login actually used?
    Read the daemon's log — `journalctl -u sshd` — where an accepted public-key login records the key type and its `SHA256:` fingerprint alongside the user and source address. Match that against `ssh-keygen -lf` output for each line in the account's `authorized_keys`. The comment field on the line is a human label and is not proof.
  • When does raising verbosity on the client tell you nothing useful at all?
    When the failure happens before authentication. If the output stalls at the connection attempt, or you get a refusal or timeout at the transport level, the problem is reachability — no listener, a filtered path, or the wrong address or port. Verbose authentication debugging only applies once the key exchange has completed and methods are being negotiated.

saying these in an interview costs you the question

  • Reads -vvv expecting the server's rejection reason
  • Never checks the server auth log
  • Assumes an offered key means an installed key
  • Confuses a connection failure with an auth failure
  • Ignores the accepted-methods list from the server

context