What you'll learn
Quick Answer
Permission denied (publickey) means the server accepts only key-based authentication and your client offered no key it recognises. Either you have not generated a key, the key is not loaded into ssh-agent, or the matching public key was never added to your GitHub account. Generate one with ssh-keygen, add it to the agent with ssh-add, paste the .pub file into GitHub's SSH keys settings, and confirm with ssh -T git@github.com.
What 'Permission denied (publickey)' really means
$ git clone git@github.com:you/fee-tracker.git
Cloning into 'fee-tracker'...
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.The word in brackets is the list of authentication methods still available to you, and it contains exactly one entry. GitHub does not accept passwords over SSH at all, so there is nothing to type. Your client offered its keys, the server recognised none of them, and the conversation ended.
SSH key authentication uses a pair of files. The private key stays on your machine and never moves. The public key, ending in .pub, is a short text file you give to servers. When you connect, the server sends a challenge that only the matching private key can answer, and no secret ever crosses the network.
The practical consequence: the fix is always on one of two ends. Either your machine is not offering the right private key, or GitHub does not have the matching public key on file. Nothing else is involved.
One warning before you touch anything. If the text you pasted into GitHub begins with -----BEGIN OPENSSH PRIVATE KEY-----, you have uploaded your private key. Delete it from GitHub, delete the key pair locally, and generate a fresh one. A public key always begins with ssh-ed25519 or ssh-rsa and fits on a single line.
Check whether you already have a key
Do not generate a second key before checking. Many people already have one from an earlier setup and end up with two keys, neither of which is the one being offered.
# macOS / Linux / Git Bash on Windows
ls -al ~/.ssh
# Windows PowerShell
Get-ChildItem $env:USERPROFILE\.sshYou are looking for a pair such as id_ed25519 and id_ed25519.pub, or the older id_rsa and id_rsa.pub. If the folder does not exist or holds only known_hosts, you have no key and the next section applies.
If a key does exist, find out what is actually happening during the handshake. The verbose flag is the single most useful debugging tool here:
ssh -vT git@github.comRead the output for three lines. Offering public key: tells you which keys your client tried. Authentications that can continue: publickey confirms the server wants a key. And No more authentication methods to try means everything you offered was rejected.
If no Offering line appears at all, your key is not loaded and the agent section is your fix. If a key is offered and still rejected, that specific public key is not on your GitHub account, and the upload section is your fix. This one command separates the two halves of the problem in about five seconds, which beats guessing.
Generate a key and load it into the agent
Generate an Ed25519 key. It is short, fast and well supported. Use RSA only if you are connecting to something old that rejects Ed25519.
ssh-keygen -t ed25519 -C "your_email@example.com"
# if the server is old and refuses ed25519
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"Press Enter to accept the default path. The passphrase prompt is optional but worth using: without one, anybody who copies that file has your GitHub push access. The agent means you type it once per session, not once per push.
Now start the agent and load the key. This step differs by operating system, and skipping it is the commonest reason a correctly uploaded key still fails.
# macOS / Linux / Git Bash
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# macOS, to remember the passphrase in the keychain
ssh-add --apple-use-keychain ~/.ssh/id_ed25519On Windows, OpenSSH ships an ssh-agent Windows service that is disabled by default. Run PowerShell as Administrator once to enable it:
Get-Service ssh-agent | Set-Service -StartupType Automatic
Start-Service ssh-agent
ssh-add $env:USERPROFILE\.ssh\id_ed25519On Linux and macOS, permissions matter. SSH refuses to use a private key whose file is readable by others, printing a loud UNPROTECTED PRIVATE KEY FILE warning and then skipping that key, which looks like the key was never offered at all:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pubWindows uses NTFS permissions rather than Unix modes, so chmod is not meaningful there. Confirm the key is loaded with ssh-add -l, which lists every key the agent currently holds.
Add the public key to GitHub and test it
Copy the contents of the .pub file. Every one of these prints or copies the same single line:
# print it and copy manually
cat ~/.ssh/id_ed25519.pub
# macOS
pbcopy < ~/.ssh/id_ed25519.pub
# Windows PowerShell
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | Set-Clipboard
# Linux with xclip installed
xclip -selection clipboard < ~/.ssh/id_ed25519.pubIn GitHub, go to Settings, then SSH and GPG keys, then New SSH key. Give it a title naming the machine, such as college laptop, keep the type as Authentication Key, and paste. Paste the whole line including the ssh-ed25519 prefix and the trailing comment, and do not add line breaks.
Now test:
ssh -T git@github.comThe reply is the part that confuses everyone:
Hi asha-dev! You've successfully authenticated,
but GitHub does not provide shell access.That is success. The second clause is not a rejection. GitHub only speaks Git over SSH, so it has nothing to give you beyond confirming who you are. The word to look for is your own username after Hi. If the username shown is not yours, you have loaded someone else's key, which happens on shared lab machines more often than you would expect.
On the very first connection you will also be asked to confirm GitHub's host fingerprint. Type yes; that adds it to known_hosts and is unrelated to your own key.
If authentication succeeds but a specific push fails with a repository-not-found or access-denied message, the key is fine and the problem is authorisation. Your account simply does not have write access to that repository.
HTTPS versus SSH remotes, and campus networks
Check which protocol your repository is using before debugging anything else, because SSH fixes do nothing for an HTTPS remote:
git remote -v
# SSH: git@github.com:you/fee-tracker.git
# HTTPS: https://github.com/you/fee-tracker.gitSwitching is a one-line change and does not affect your files or history:
git remote set-url origin git@github.com:you/fee-tracker.gitThe trade-off is simple. HTTPS works through almost any network and firewall, but GitHub no longer accepts your account password for Git operations, so you must generate a personal access token and use that as the password. SSH needs one-time setup and then never prompts again, which is why most people prefer it once it works.
The campus and office network problem. SSH uses port 22, and many college labs, corporate networks and public Wi-Fi block outbound port 22 entirely. The symptom is a connection that hangs and then times out, or Connection refused, rather than a clean permission error. GitHub provides an alternative endpoint on port 443, the same port HTTPS uses, which is almost never blocked:
ssh -T -p 443 git@ssh.github.comIf that works, make it permanent in ~/.ssh/config:
Host github.com
HostName ssh.github.com
Port 443
User git
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yesThat same config file solves the multiple-keys problem. If you have a personal key and a work key, the agent offers them in whatever order it holds them, and some servers cut you off after a few failed attempts with Too many authentication failures. IdentitiesOnly yes forces SSH to offer only the key you named for that host, which makes the behaviour predictable instead of order-dependent.
One last detail that catches people using a laptop for both college and freelance work. GitHub will not let the same public key be attached to two accounts, so a second account needs a second key pair plus its own Host entry with an invented alias such as github-work, and remotes written as git@github-work:org/repo.git. Set user.email per repository as well, otherwise your commits carry the wrong identity even though the push succeeds.
