Understanding the 'Permission denied (publickey)' Error
When you see Permission denied (publickey) in your terminal after running a Git command like git push, git pull, or git clone, it means the SSH authentication between your local machine and the remote Git server has failed. The server is rejecting your connection because it cannot verify your identity through public key cryptography.
This error typically appears in one of these forms:
Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
Or sometimes with more detail:
git@github.com: Permission denied (publickey).
Why This Error Matters
SSH public key authentication is the primary secure method for connecting to remote Git hosting services like GitHub, GitLab, and Bitbucket. When it breaks, you lose the ability to push code, pull updates, or clone repositories — effectively halting your development workflow. Understanding how to diagnose and fix this issue is an essential skill for any developer who collaborates through Git.
Prerequisites: How SSH Authentication Works with Git
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Before diving into fixes, it helps to understand the moving parts involved in a successful SSH Git connection:
- SSH Key Pair: A private key (usually
id_ed25519orid_rsa) stored locally and a public key (.pubextension) registered with your Git hosting provider - ssh-agent: A background program that holds your decrypted private keys in memory so you don't have to enter the passphrase every time
- Remote Git URL: Must use the SSH format:
git@github.com:username/repo.gitrather than HTTPS - Hosting Service Configuration: Your public key must be added to your account settings on GitHub, GitLab, or Bitbucket
If any link in this chain is broken, the authentication fails and you see the dreaded publickey error.
Step-by-Step Troubleshooting Guide
Step 1: Verify You Have an SSH Key Pair
First, check whether an SSH key exists on your system. List the contents of your .ssh directory:
ls -la ~/.ssh
A healthy output looks something like:
-rw------- 1 user staff 464 Feb 10 10:30 id_ed25519
-rw-r--r-- 1 user staff 101 Feb 10 10:30 id_ed25519.pub
-rw------- 1 user staff 2602 Jan 15 09:20 id_rsa
-rw-r--r-- 1 user staff 571 Jan 15 09:20 id_rsa.pub
If you see no files or the directory doesn't exist, you need to generate a new SSH key pair. Use the modern Ed25519 algorithm (preferred) or RSA as a fallback:
# Recommended: Ed25519 (faster, more secure)
ssh-keygen -t ed25519 -C "your_email@example.com"
# Fallback for older systems that don't support Ed25519
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
When prompted, you can press Enter to accept the default file location. Optionally, set a passphrase for additional security. The command will generate both the private key and the .pub public key file.
Step 2: Add Your SSH Key to the SSH Agent
Even if you have a key pair, the ssh-agent might not be running or might not have your key loaded. Start the agent and add your key:
# Start the ssh-agent in the background
eval "$(ssh-agent -s)"
# Add your private key (adjust filename if using id_rsa or custom name)
ssh-add ~/.ssh/id_ed25519
On macOS, if you're using the system keychain integration, you can add the key permanently with:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
On Linux, to ensure the key persists across reboots, you can configure your shell profile or use a keychain management tool. Verify the key is loaded:
ssh-add -l
You should see a fingerprint entry like:
256 SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx user@hostname (ED25519)
If you see The agent has no identities., the key was not added successfully. Re-run the ssh-add command and check for typos in the path.
Step 3: Add Your Public Key to Your Git Hosting Provider
This is the most commonly missed step. The public key must be registered with your Git hosting service. Copy your public key to the clipboard:
# On macOS
pbcopy < ~/.ssh/id_ed25519.pub
# On Linux (with xclip installed)
xclip -selection clipboard < ~/.ssh/id_ed25519.pub
# On Windows (PowerShell)
Get-Content ~/.ssh/id_ed25519.pub | Set-Clipboard
# Universal fallback: display and manually copy
cat ~/.ssh/id_ed25519.pub
Now add the key to your hosting provider:
- GitHub: Settings → SSH and GPG keys → New SSH key → Paste the key and save
- GitLab: Preferences → SSH Keys → Paste the key → Add key
- Bitbucket: Personal settings → SSH keys → Add key → Paste and save
- Azure DevOps: User settings → SSH public keys → Add key
Ensure you copy the entire key including the ssh-ed25519 or ssh-rsa prefix and the trailing email comment. A partial key will not work.
Step 4: Test the SSH Connection Explicitly
Before troubleshooting Git itself, test the SSH connection directly. This isolates whether the problem is with SSH or with Git's configuration:
# Test GitHub
ssh -T git@github.com
# Test GitLab
ssh -T git@gitlab.com
# Test Bitbucket
ssh -T git@bitbucket.org
A successful response looks like:
# GitHub success message
Hi username! You've successfully authenticated, but GitHub does not provide shell access.
# GitLab success message
Welcome to GitLab, @username!
# Bitbucket success message
authenticated via a deploy key or similar message
If you still get Permission denied (publickey) here, the problem is with your SSH setup, not Git. Revisit steps 1 through 3. If you see a success message, move on to step 5.
Step 5: Verify Your Git Remote URL Uses SSH
If your SSH connection works but Git commands still fail, check that your repository's remote URL uses the SSH protocol, not HTTPS:
# List remote URLs
git remote -v
You should see something like:
origin git@github.com:username/repository.git (fetch)
origin git@github.com:username/repository.git (push)
If instead you see an HTTPS URL like https://github.com/username/repository.git, you need to switch to SSH:
# Change the remote URL from HTTPS to SSH
git remote set-url origin git@github.com:username/repository.git
For repositories you haven't cloned yet, make sure you use the SSH clone URL. On GitHub, this is available by clicking the "SSH" tab in the clone dialog. The format is always:
git clone git@github.com:username/repository.git
Step 6: Check SSH Config File for Host Aliases
If you use an SSH config file (~/.ssh/config) to define host aliases or custom settings, a misconfiguration there can cause authentication failures. Inspect your config:
cat ~/.ssh/config
A typical entry for GitHub might look like:
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
Common issues in the SSH config file:
- Wrong IdentityFile path: The file path must point to your actual private key file
- Missing User directive: For Git hosting services, the SSH user is always
git(not your username) - Host name mismatch: If you defined an alias like
Host github-personal, you must use that alias in your Git remote URLs - IdentitiesOnly not set: Without this, SSH may offer keys in an order the server rejects before reaching the correct one
If you use multiple SSH keys for different accounts (e.g., work and personal GitHub accounts), your config should distinguish them with aliases:
# Personal GitHub account
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
# Work GitHub account
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
Then use the aliases in your remote URLs:
git remote add origin git@github-personal:username/repo.git
Step 7: Use Verbose SSH Output for Deep Debugging
When all else fails, enable verbose SSH logging to see exactly which keys are being offered and where the authentication breaks down:
# Verbose mode shows detailed handshake information
ssh -vT git@github.com
# Even more detail for stubborn issues
ssh -vvT git@github.com
Look for key lines in the output:
debug1: Offering public key: /Users/username/.ssh/id_ed25519
debug1: Server accepts key: /Users/username/.ssh/id_ed25519
If you see the server rejecting every key offered, the public key isn't registered with the hosting service. If a specific key isn't being offered at all, the agent doesn't have it loaded or the config isn't pointing to the right file. The verbose output is your definitive diagnostic tool for stubborn cases.
Step 8: Check File Permissions
SSH is notoriously strict about file permissions. Incorrect permissions on your .ssh directory or key files will cause SSH to refuse to use them. Run these commands to set the correct permissions:
# Set .ssh directory permissions (read/write/execute for owner only)
chmod 700 ~/.ssh
# Set private key permissions (read/write for owner only)
chmod 600 ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_rsa
# Set public key permissions (read/write for owner, read for everyone)
chmod 644 ~/.ssh/id_ed25519.pub
chmod 644 ~/.ssh/id_rsa.pub
# Set known_hosts permissions
chmod 644 ~/.ssh/known_hosts
# Set config file permissions
chmod 644 ~/.ssh/config
On Linux and macOS, SSH will silently ignore keys with group or world-readable permissions on private key files. If you recently migrated keys from another machine, this is a common pitfall.
Step 9: Handle SSH Agent Socket Issues (WSL and Docker)
If you're using Windows Subsystem for Linux (WSL) or running Git inside a container, the SSH agent socket might not be accessible. For WSL users, consider using the Windows-native OpenSSH agent or forwarding the socket:
# In WSL, check if ssh-agent is running
eval "$(ssh-agent -s)"
# Alternatively, use ssh-agent from Windows side
# Add to your ~/.bashrc or ~/.zshrc
export SSH_AUTH_SOCK=/mnt/c/Users/YourWindowsUser/.ssh/agent.sock
For Docker containers, either mount the host's .ssh directory (be cautious with security) or use build secrets to inject keys during image builds.
Common Scenarios and Quick Fixes
Scenario A: "I just got a new computer"
You need to generate a brand new SSH key pair on the new machine and add the public key to your hosting provider. SSH keys are machine-specific and should not be copied between machines (though you can copy them securely if you understand the risks). Generate a new key, add it to your provider, and you're good to go.
Scenario B: "It works for one repo but not another"
This often means you're using different remote URLs. Check each repository's remote with git remote -v. One might be using HTTPS while the problematic one uses SSH, or the SSH URL might point to a different host (e.g., a self-hosted GitLab instance) that doesn't have your key.
Scenario C: "I use multiple GitHub accounts"
Use the SSH config host alias technique from Step 6. Create separate keys for each account, register each public key with its respective GitHub account, and use the aliases in your remote URLs. Remember: one SSH key cannot be associated with multiple GitHub accounts simultaneously.
Scenario D: "The error happens intermittently"
This could indicate a network issue, a corporate firewall interfering with SSH traffic, or an ssh-agent that's losing its state. Try:
# Check if ssh-agent is still running
ps aux | grep ssh-agent
# Restart the agent and reload keys if needed
killall ssh-agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
Also verify that port 22 (the default SSH port) is not being blocked on your network. Some corporate networks block SSH on port 22. You can use HTTPS as a fallback or configure SSH to use port 443 if your hosting provider supports it.
Preventive Best Practices
- Use Ed25519 keys: They are faster, more secure, and shorter than RSA keys. Modern Git hosting services all support them. Generate with
ssh-keygen -t ed25519 -C "your_email@example.com" - Set a passphrase: Always protect your private key with a strong passphrase. If your machine is compromised, the passphrase is your last line of defense
- Keep a backup of your keys: Store an encrypted backup of your SSH keys in a password manager or secure offline storage. Losing your private key means losing access to repositories configured for SSH-only access
- Test SSH after key generation: Immediately run
ssh -T git@github.comafter adding a new public key to verify everything works end-to-end - Document your SSH configuration: If you use multiple keys or custom configs, keep a commented
~/.ssh/configfile that explains each entry. Your future self will thank you - Use
IdentitiesOnly yesin SSH config: This prevents SSH from offering all available keys to the server, which can cause authentication failures if too many keys are tried before the correct one - Periodically rotate keys: Like passwords, SSH keys benefit from occasional rotation. Generate new keys annually and update your hosting providers
- Verify remote URLs before cloning: Make a habit of checking whether you're using the SSH or HTTPS clone URL. Many developers keep both remotes configured (e.g.,
originas SSH andupstreamas HTTPS for read-only access)
When All Else Fails: Switch to HTTPS with Token Authentication
If you've exhausted all troubleshooting steps and SSH still won't cooperate — perhaps due to an unresolvable network restriction — you can fall back to HTTPS with a personal access token. This is a perfectly valid authentication method:
# Change remote to HTTPS
git remote set-url origin https://github.com/username/repository.git
# On push/pull, enter your username and use a personal access token as the password
# Generate tokens at: GitHub Settings → Developer settings → Personal access tokens
To avoid entering credentials repeatedly, configure Git credential caching:
# Store credentials in memory for a session (default 15 minutes)
git config --global credential.helper cache
# Store credentials permanently on disk (macOS uses osxkeychain by default)
git config --global credential.helper store
Note that storing credentials in plaintext with store is less secure than SSH keys. Use the OS-specific credential helpers (osxkeychain, wincred, or libsecret on Linux) for a better balance of convenience and security.
Conclusion
The Permission denied (publickey) error, while frustrating, is almost always solvable by methodically working through the authentication chain: ensure you have a key pair, the agent is running with your key loaded, the public key is registered with your hosting provider, and your remote URL uses SSH. The steps outlined above — from basic checks to verbose SSH debugging — cover virtually every scenario you'll encounter. By internalizing this troubleshooting workflow and adopting the preventive best practices, you'll spend less time wrestling with authentication errors and more time writing code. Remember: SSH authentication is a chain, and the error simply means one link is broken. Find that link, fix it, and get back to building.