Ch. 16 · Git & CI/CD

Git Permission denied (publickey) on GitHub: SSH Fix

Fix git@github.com: Permission denied (publickey) and Could not read from remote repository: test SSH, load keys, set IdentityFile, or use HTTPS.

~7 min readintermediateupdated Oct 4, 2026

Your git clone, fetch, pull or push stopped with:

git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
Text

The first line comes from SSH, not Git: GitHub’s SSH server rejected every key your machine offered, or your machine offered none. The remaining lines are Git explaining that, without a connection, it could not reach the repository. (The Git lines were checked against Git 2.54.0 with OpenSSH 10.3 on macOS; the SSH line is the standard OpenSSH rejection that GitHub documents.) Git itself is fine; the fix is in your SSH keys, SSH config or remote URL.

Quick fix checklist

  • Test SSH on its own: ssh -T git@github.com. Success prints Hi <username>! You've successfully authenticated....
  • List keys the agent holds: ssh-add -l. If it says The agent has no identities., run ssh-add ~/.ssh/id_ed25519.
  • Check the public key is added to your GitHub account under Settings, SSH and GPG keys.
  • Pin the key in ~/.ssh/config with IdentityFile, especially if its file name is not a default one.
  • Look at the prefix: the error must say git@github.com. Anything else means a wrong remote URL.
  • Don’t run Git with sudo: root has different keys.
  • In a hurry: git remote set-url origin https://github.com/OWNER/REPO.git and authenticate over HTTPS.

Before you start

Know where your keys live: by default in ~/.ssh/ as pairs like id_ed25519 (private, never share it) and id_ed25519.pub (public, safe to paste into GitHub). Have access to the GitHub account that should own the key. On a work laptop, check whether your company requires SSH certificates or SSO authorization for organization repositories, since that changes what “the right key” means. Nothing below requires printing or copying a private key; only the .pub file ever leaves your machine.

Why it happens

GitHub serves every user through a single SSH account named git. It cannot tell who you are from the user name, so it identifies you only by the public key your client proves it holds. The SSH client walks through candidate keys: those in the agent, any IdentityFile from ~/.ssh/config, and default files such as ~/.ssh/id_ed25519 and ~/.ssh/id_rsa. If GitHub recognises none, the server ends with Permission denied (publickey), publickey being the only method it accepts.

The usual root causes:

  1. No key exists on this machine yet.
  2. The key exists but is not offered: a non-default file name with no IdentityFile entry, or a passphrase-protected key not loaded in the agent after a reboot.
  3. The public key was never added to GitHub, or was added to a different account.
  4. Wrong user in the URL: github.com:acme/app.git without git@ makes SSH log in as your local user name, and the error starts with aditya@github.com: instead.
  5. sudo git ... runs as root, which reads /var/root/.ssh or /root/.ssh, not yours.

A related but different failure: if the key belongs to another GitHub account (a personal one on a work repo), SSH succeeds and GitHub refuses afterwards with ERROR: Permission to acme/shop-api.git denied to other-user. That is an authorization problem on a correct connection.

Step-by-step walkthrough

Step 1: Test the SSH connection without Git

ssh -T git@github.com
Terminal

On success GitHub replies with Hi USERNAME! You've successfully authenticated, but GitHub does not provide shell access. and the exit code is 1, which is normal since no shell is allowed. The user name tells you which account your key belongs to. If it says Permission denied (publickey)., the problem is purely SSH and Git is out of the picture.

Step 2: Read the verbose log

ssh -vT git@github.com 2>&1 | grep -E 'identity file|Offering|Authentications|No more'
Terminal

The lines to read, whose exact format varies a little between OpenSSH versions:

  • identity file /Users/you/.ssh/id_ed25519 type ...: a candidate key SSH will consider (type -1 means the file does not exist).
  • Offering public key: ...: this key was actually sent to GitHub.
  • Authentications that can continue: publickey: GitHub rejected the previous key.
  • No more authentication methods to try.: every key failed.

If the key you expect never appears under Offering, it is not being used (cause 2). If it is offered and rejected, GitHub does not know it (cause 3).

Step 3: Load the key and pin it in your SSH config

Check the agent:

$ ssh-add -l
The agent has no identities.
$ ssh-add ~/.ssh/id_ed25519
Identity added: /Users/you/.ssh/id_ed25519 (you@example.com)
Text

If ssh-add instead prints Could not open a connection to your authentication agent., no agent is running in this shell; start one with eval "$(ssh-agent -s)". On macOS, store the passphrase in the Keychain with ssh-add --apple-use-keychain ~/.ssh/id_ed25519 (older macOS versions used -K).

Then make the choice permanent in ~/.ssh/config:

Host github.com
  User git
  AddKeysToAgent yes
  UseKeychain yes
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes
ini

UseKeychain is macOS-only; drop it elsewhere, or add IgnoreUnknown UseKeychain above it if the file is shared across systems. IdentitiesOnly yes stops SSH from offering every key in the agent, which avoids Too many authentication failures when you have many keys.

Step 4: Create a key and add it to GitHub if you have none

ssh-keygen -t ed25519 -C "you@example.com"
pbcopy < ~/.ssh/id_ed25519.pub      # macOS; elsewhere: cat the .pub file
Terminal

ssh-keygen also prints the key’s fingerprint, the same format ssh-keygen -lf ~/.ssh/id_ed25519.pub shows: 256 SHA256:... you@example.com (ED25519). Paste the .pub content into GitHub under Settings, SSH and GPG keys, New SSH key. GitHub lists each key’s SHA256 fingerprint there, so you can match it against ssh-add -l to be sure the loaded key is the registered one.

Step 5: Or switch the remote to HTTPS

When SSH is blocked or you need to push right now:

$ git remote -v
origin	git@github.com:acme/shop-api.git (fetch)
origin	git@github.com:acme/shop-api.git (push)
$ git remote set-url origin https://github.com/acme/shop-api.git
Text

HTTPS authenticates with a personal access token or a credential helper such as Git Credential Manager or gh auth login, not your account password. If SSH fails with Connection timed out rather than Permission denied, port 22 is blocked; GitHub also accepts SSH on port 443 via ssh.github.com.

Worked scenario

Ravi uses a personal GitHub account and a work account on one laptop. He cloned a work repository and the push fails:

$ git push
ERROR: Permission to acme/payments.git denied to ravi-personal.
fatal: Could not read from remote repository.
Text

Diagnosis: ssh -T git@github.com replies Hi ravi-personal!. The agent offers his personal key first, GitHub accepts it, and then correctly refuses because that account has no access to acme/payments. Adding the personal key to the work account is not possible (GitHub allows a key on one account only), and it would be the wrong fix anyway.

Fix: one key per account, selected by a host alias.

Host github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_work
  IdentitiesOnly yes

Host github-personal
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_personal
  IdentitiesOnly yes
ini

Work repositories keep git@github.com:acme/payments.git. Personal ones use the alias: git remote set-url origin git@github-personal:ravi/dotfiles.git. To check what SSH will do for each name without connecting, ask it to print the resolved config:

$ ssh -G github-personal | grep -E '^(hostname|identityfile) '
hostname github.com
identityfile ~/.ssh/id_ed25519_personal
Text

Common mistake

Running sudo git push or sudo chmod on ~/.ssh. The error persists under sudo because root has no keys of yours, and loose permissions make SSH ignore private keys. Private keys should be readable only by you (chmod 600 ~/.ssh/id_ed25519).

Pasting the private key into GitHub, or copying it between machines. GitHub needs only the .pub file. Generate one key per machine instead; it can be revoked individually when a laptop is lost.

Regenerating the key on every failure. If ssh -vT shows the key being offered and accepted (Hi user!), the key is fine and the problem is the account or the URL. A new key just adds another one to clean up later.

Verify the behavior

ssh -T git@github.com            # expect: Hi <the account that owns this repo>!
ssh-add -l                       # expect: the key's SHA256 fingerprint listed
git ls-remote origin HEAD        # expect: a commit hash followed by HEAD
Terminal

git ls-remote is the cheapest Git-level proof: it authenticates and reads one ref without changing anything. Then retry the original git push.

Interview exercise

“A new developer gets git@github.com: Permission denied (publickey) on their first clone, but ls ~/.ssh shows id_ed25519 and id_ed25519.pub. How do you debug this systematically?”

Answer and reasoning

I would separate SSH from Git first with ssh -T git@github.com. If that also fails, Git is irrelevant. Next, ssh -vT git@github.com tells me whether id_ed25519 is offered at all. With that default file name it normally is, so if it is offered and rejected, GitHub does not recognise the public key: I would compare ssh-keygen -lf ~/.ssh/id_ed25519.pub with the fingerprints in the developer’s GitHub settings, and usually find it was never added, or added to another account.

If the key is not offered, I would check for a passphrase-protected key not loaded in the agent (ssh-add -l), a ~/.ssh/config entry pointing elsewhere, or file permissions SSH refuses to use. I would also check the remote URL starts with git@github.com: and that the command was not run with sudo. If ssh -T succeeds as a different user, the issue is authorization (wrong account or missing org access), not authentication, and the answer is a per-account host alias or HTTPS.

Continue learning

More in Git & CI/CD

esc