BlueByte
publickeyFixed

SSH: Permission denied (publickey)

By Haneul SeoUpdated August 26, 20263 min

Hi, it's BlueByte. "Permission denied (publickey)" feels like a wall, but SSH will tell you exactly why if you ask it verbosely — you do not have to guess. Let's read what your client actually offered and what the server said, then fix the one thing that failed.

What the message means

A connection ends before you get a shell:

git@github.com: Permission denied (publickey).

The server offered public-key authentication, none of the keys your client sent were accepted, and there was no password fallback. Which of those failed decides the fix — so ask SSH rather than guessing.

The three things that usually fail

Almost every case is one of these:

  • Your key is not loaded into the agent, or the key file is not where SSH looks.
  • The client is sending a different key than the one the server trusts — common when you have several keys and the agent offers the wrong one first.
  • The server refuses the key over permissions — your ~/.ssh directory or the private key file is readable by others, and SSH ignores keys with loose permissions.

Ask SSH which one it is

Run the connection verbosely and read which keys it offers and how the server responds:

ssh -vT git@github.com

Look for Offering public key: ... followed by the server's answer. No key offered means it is not loaded; a key offered and rejected means the wrong key or a server that does not trust it; a Permissions ... are too open warning means the file mode is the problem. The verbose trace removes the guesswork.

Fix the one that failed

  1. No key offered — add yours to the agent:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
  1. Wrong key offered — pin the right one per host:
# ~/.ssh/config
Host github.com
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes
  1. Rejected over permissions — tighten them; SSH ignores keys others can read:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519

A real case: the agent offered an old key

You clone over SSH and get Permission denied (publickey). ssh -vT git@github.com shows it offering id_rsa (an old key) and the server rejecting it, never reaching your newer id_ed25519. So it is key selection, not a missing key. You add a Host github.com block with IdentityFile ~/.ssh/id_ed25519 and IdentitiesOnly yes, re-run ssh -vT, and this time it offers only the ed25519 key, which the server accepts with "successfully authenticated". The clone works.

Confirm the key is accepted

Test the authentication path without opening a shell:

ssh -T git@github.com

A greeting like Hi <user>! You've successfully authenticated (even though it then closes the connection) means the key is accepted.

Keep it from recurring

Keep IdentitiesOnly yes in your SSH config so the agent does not throw every key at a server and trip a limit on attempts, register each new machine's public key with the service once, and keep ~/.ssh permissions tight so a synced dotfiles setup never loosens them.

How this differs from a connection or host-key error

Permission denied (publickey) is authentication failing after a connection. Connection refused or Connection timed out are earlier — the server is unreachable or nothing is listening on port 22 — and Host key verification failed is a changed server key, a different problem entirely. When you hit the publickey error, run ssh -vT first; the answer is almost always in that trace.

Related questions

ssh-add says 'Could not open a connection to your authentication agent'.

The agent is not running in this shell. Start it with eval "$(ssh-agent -s)" first, then ssh-add your key.

It works as my user but not with sudo. Why?

sudo runs as root, which has its own ~/.ssh and agent. Either do not use sudo for the SSH command, or configure the key for root explicitly.

The right key is added but the server still rejects it.

Confirm the matching public key is registered on the server or your account, and that you are using the SSH remote URL (git@host:org/repo.git), not HTTPS.

Why does IdentitiesOnly matter?

Without it the agent offers every key it holds; a server that limits authentication attempts can lock you out before it reaches the right one. IdentitiesOnly sends only the key you configured.

ssh -vT shows 'Too many authentication failures'.

The agent offered too many keys before the right one. Set IdentitiesOnly yes and point IdentityFile at the correct key so only it is tried.

References

Haneul Seo

Infrastructure engineer · 10+ years running Linux fleets

More in this category

Start request repeated too quicklyFixed

systemd: Start request repeated too quickly

systemd refuses to start a unit that was started more than StartLimitBurst times (default 5) within StartLimitIntervalSec (default 10s), and Restart= counts against that limit. With the 100 ms default RestartSec a crashing service burns all five attempts in under a second. Find the real crash in the journal, fix it, run reset-failed, and give restarts room with RestartSec.

systemd
Too many authentication failuresFixed

SSH: Received disconnect ... Too many authentication failures

Your agent is offering more keys than the server will let you try. Every public key sshd looks at burns one of the MaxAuthTries attempts — six by default, often three on a hardened host — so the right key never gets its turn and the server hangs up before you type anything. IdentitiesOnly=yes with an explicit IdentityFile pins the connection to one key and the attempt count drops to one.

OpenSSH
1722Fixed

Active Directory: replication fails with error 1722, The RPC server is unavailable

RPC reports 1722 (0x6ba, RPC_S_SERVER_UNAVAILABLE) when a lower layer fails to connect, so the real fault is almost never RPC itself — it is DNS, a blocked port, or a host-side setting on one of the two domain controllers. repadmin tells you which partner is failing, dcdiag /test:dns rules out name resolution, and Test-NetConnection plus the dynamic port range settle the firewall question. The most common miss is a rule that allows TCP 135 but not 49152–65535.

Windows Server Active Directory
Windows Server RDSFixed

Windows Server RDS: The remote session was disconnected because there are no Remote Desktop License Servers available to provide a license

The 120-day RD Licensing grace period ended and the session host has no usable license server, so it refuses sessions. GetGracePeriodDays returning DaysLeft 0 and an empty SpecifiedLSList confirm it in seconds. The fix is a real, activated license server with CALs that are new enough for the host — a 2019 CAL cannot serve a 2022 session host — configured through the deployment or the Licensing policies, plus RPC ports open between the two.

Windows Server RDS
0x80070035 / Event ID 31017Workaround

Windows 11: "Your organization's security policies block unauthenticated guest access" when opening a NAS share (0x80070035)

The SMB client on Windows 10 Enterprise/Education/Pro for Workstations, Windows 11 Pro and Windows Server 2019+ refuses guest logons by default, and Windows 11 24H2 Enterprise/Pro/Education also requires SMB signing, which guest sessions can't do. A NAS share that only offers guest access therefore fails with the 'block unauthenticated guest access' dialog, Error code 0x80070035, or System error 3227320323, and Event ID 31017 'Rejected an insecure guest logon' lands in the SmbClient/Security log. The fix Microsoft recommends is a real account on the NAS and signing support in its firmware; Set-SmbClientConfiguration -EnableInsecureGuestLogons $true (plus -RequireSecuritySignature $false on 24H2) is the escape hatch, and it costs you signing and encryption on that client.

Windows (SMB client)
E: Could not get lock /var/lib/dpkg/lock-frontendFixed

Ubuntu/Debian: E: Could not get lock /var/lib/dpkg/lock-frontend — who holds it and how to wait for it

Another package manager, usually Ubuntu's unattended-upgrades fired by a persistent systemd timer at boot, holds the dpkg frontend lock while your apt-get runs. apt-get gives up at once while apt waits because Ubuntu ships binary::apt::DPkg::Lock::Timeout "120" for the apt binary only. Read the PID from the message, let the run finish or pass -o DPkg::Lock::Timeout=<seconds> to apt-get, run dpkg --configure -a only after a genuinely interrupted run, and never delete the lock file: it is an fcntl lock the kernel releases when the holder exits.

APT (Ubuntu/Debian)