Maintained guide, verified 2026-09-01. The original 2017 post is substantive and is preserved at the end. Its historical commands—including an unverified
ssh-keyscanappend—are archive material, not current instructions. The guide below separates host identity, client authentication, agent signing, Git routing, and server authorization before changing anything.
Table of Contents
First classify the failure
Three messages that appear together can describe different layers:
| Evidence in the trace | Layer | What it means |
|---|---|---|
REMOTE HOST IDENTIFICATION HAS CHANGED or Host key verification failed |
Server identity | The client did not establish trust in the server key. Do not troubleshoot user keys yet. |
Permission denied (publickey) |
User authentication | The server did not complete public-key authentication for the requested username. The cause is not necessarily a missing key. |
sign_and_send_pubkey: ... agent refused operation |
Client signing | SSH found or offered an identity, but the agent/provider would not perform the signature. Reinstalling the public key may not help. |
fatal: Could not read from remote repository |
Git summary | Git reports that the SSH transport failed; it does not identify which SSH layer failed. |
Do not delete known_hosts, regenerate every key, or change server permissions all at once. Each destroys evidence and can turn one failure into several.
Start with a read-only evidence pass
Run these from the same account, terminal, container, IDE, or CI job that runs Git. sudo git push can select another home directory, config, and agent, so avoid elevation.
git remote -v
git remote get-url --all origin
git remote get-url --push --all origin
ssh -G git-prod |
awk '$1 ~ /^(hostname|user|port|identityfile|identitiesonly|identityagent)$/ { print }'
ssh -vvv -o BatchMode=yes -o ConnectTimeout=10 -T git-prod
ssh-add -l -E sha256
printf 'SSH_AUTH_SOCK=%s\n' "${SSH_AUTH_SOCK:-<unset>}"
if [ -n "${SSH_AUTH_SOCK:-}" ] && [ -S "$SSH_AUTH_SOCK" ]; then
printf '%s\n' 'agent socket exists'
else
printf '%s\n' 'agent socket is absent or not a socket'
fi
Replace git-prod with the exact SSH host or alias from the remote. ssh -G prints the effective client configuration and exits. ssh -vvv ... BatchMode=yes makes an authentication attempt without password or host-confirmation prompts; it does not push Git data. Against an ordinary shell server, -T may still open a noninteractive session, so stop it after the diagnostic output if the server does not exit on its own.
Debug logs may contain account names, hostnames, IP addresses, and local paths. Redact those before sharing, but keep algorithm names, fingerprint values, and the sequence of Offering public key, Server accepts key, and failure lines.
Read the verbose trace in order
- Destination: confirm
Connecting to ..., port, andAuthenticating to ... as .... A perfect key for the wrong hostname or username still fails. - Host verification: find the selected host-key algorithm and fingerprint. A host-key error occurs before user authentication.
- Identity discovery:
identity file ... type -1means that particular path was not found.Offering public keyshows what was actually offered. - Server response: repeated
Authentications that can continue: publickeyafter an offer usually means the server did not accept that username/key combination or policy. - Signature: if
Server accepts keyis followed bysign_and_send_pubkey: ... agent refused operation, focus on the selected agent, key constraints, unlock/confirmation state, or hardware provider. - Repository access: only after SSH authentication succeeds should Git repository path and authorization be investigated.
Save a redacted trace with the client version and time. Do not treat one line copied from another machine as evidence for this session.
Verify the host key safely
ssh-keyscan retrieves whatever key the network endpoint presents; it does not authenticate that key. OpenSSH warns that constructing known_hosts from unverified scan output exposes users to man-in-the-middle attacks.
Obtain the expected SHA-256 fingerprint through an authenticated or out-of-band channel: the provider’s signed-in console or official documentation, a server administrator over a separately verified channel, or the server console itself. Then compare without changing known_hosts:
ssh-keygen -F git.example.com
scan_file=$(mktemp)
ssh-keyscan -T 5 -t ed25519 git.example.com >"$scan_file"
ssh-keygen -lf "$scan_file" -E sha256
For a nonstandard port, the known-hosts name is normally [git.example.com]:2222. For GitHub, compare against GitHub’s current official fingerprint page; do not trust a fingerprint copied from a forum or this article.
The ed25519 scan above is an example, not a compatibility claim. If the authenticated source publishes a different host-key type, request and compare that exact offered type instead of weakening client policy by guesswork.
Only after an exact out-of-band match should you back up and edit the entry:
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
if [ -f "$HOME/.ssh/known_hosts" ]; then
known_hosts_backup="$HOME/.ssh/known_hosts.pre-change.$(date +%Y%m%d%H%M%S)"
cp -p -- "$HOME/.ssh/known_hosts" "$known_hosts_backup"
fi
cat "$scan_file" >>"$HOME/.ssh/known_hosts"
chmod 600 "$HOME/.ssh/known_hosts"
rm -f -- "$scan_file"
If a saved key changed, investigate rotation or compromise first. After verification and backup, remove only the exact stale name with ssh-keygen -R git.example.com (or the bracketed host-and-port form), then add the verified replacement. Never solve this with StrictHostKeyChecking no, an empty known_hosts, or a blind scan append.
Check the identity and agent
Inventory fingerprints without printing private material:
ssh-add -l -E sha256
ssh-keygen -lf "$HOME/.ssh/id_ed25519_git_prod.pub" -E sha256
ls -ld "$HOME/.ssh" "$HOME/.ssh/config"
ls -l "$HOME/.ssh/id_ed25519_git_prod" "$HOME/.ssh/id_ed25519_git_prod.pub"
Compare the public-key fingerprint with the key registered for the Git account or present in the target user’s authorized_keys. Never copy a private key to a server, ticket, chat, repository, or shared directory. The server needs only the one-line public key.
On OpenSSH versions that support it, test whether the agent can actually sign with the corresponding public key:
ssh-add -T "$HOME/.ssh/id_ed25519_git_prod.pub"
Listing a key with ssh-add -l proves only that the agent advertises it. A sign test or real SSH attempt can still be refused because:
- a desktop keychain, password manager, or managed agent is locked;
- the identity requires per-use confirmation and no usable prompt is available;
- a FIDO/security key is absent, locked, or waiting for touch/PIN;
SSH_AUTH_SOCKpoints to a stale or unintended agent;IdentityAgentin the effective config overridesSSH_AUTH_SOCK;- a forwarded or destination-constrained agent refuses this path;
- the provider does not support the requested signature operation.
Unlock or confirm in the owning agent/provider and retry. Do not begin by deleting all identities or killing desktop agents. If there is genuinely no managed agent and you want an isolated shell agent, make its lifecycle explicit:
eval "$(ssh-agent -s)"
ssh-add "$HOME/.ssh/id_ed25519_git_prod"
ssh-add -l -E sha256
# When the isolated shell test is finished:
ssh-agent -k
This is not the right remedy for a hardware-backed or organization-managed identity; repair that provider or select its correct socket instead.
Pin the exact host, user, and key
Use a specific alias rather than a broad Host * exception:
Host git-prod
HostName git.example.com
User git
Port 22
IdentityFile ~/.ssh/id_ed25519_git_prod
IdentitiesOnly yes
PubkeyAuthentication yes
IdentitiesOnly yes tells OpenSSH to use configured identities even when an agent offers many others. Confirm the result:
ssh -G git-prod |
awk '$1 ~ /^(hostname|user|port|identityfile|identitiesonly|identityagent)$/ { print }'
For GitHub, the SSH username is git, not the GitHub profile name. A self-hosted Git service may also require git, while direct EC2 login uses an operating-system account instead. Do not transfer one service’s username to another.
Client files must not be writable by other users; private identity files must not be readable by them. After preserving a backup and confirming the targets, conservative modes are:
chmod 700 "$HOME/.ssh"
chmod 600 "$HOME/.ssh/config"
chmod 600 "$HOME/.ssh/id_ed25519_git_prod"
chmod 644 "$HOME/.ssh/id_ed25519_git_prod.pub"
For an agent-only or hardware-backed identity, IdentityFile may name a public key file that selects the corresponding private key inside the agent. Follow the provider’s official setup; a hardware-key stub may instead be the configured identity file. Do not export private key material merely to make the file layout resemble this example.
Generate a new key only when needed
Do not overwrite a working identity. When client/server/provider support it, Ed25519 is a modern default; FIDO-backed Ed25519 provides hardware protection where supported. Use a unique filename and a passphrase appropriate to the environment:
umask 077
ssh-keygen -t ed25519 -a 64 \
-f "$HOME/.ssh/id_ed25519_git_prod" \
-C "git-prod"
ssh-keygen -lf "$HOME/.ssh/id_ed25519_git_prod.pub" -E sha256
For compatible FIDO hardware and servers:
umask 077
ssh-keygen -t ed25519-sk \
-f "$HOME/.ssh/id_ed25519_sk_git_prod" \
-C "git-prod hardware key"
Register only the .pub content through the service’s authenticated key-management page or an administrator’s controlled process. Keep the old key active until the new one is verified, then retire it deliberately. A new key cannot repair an unverified host-key failure, wrong username, wrong remote, or broken agent socket.
Validate the Git remote
Git can have different fetch and push URLs, and insteadOf/pushInsteadOf rewriting can change the effective destination. Inspect all of them:
git remote -v
git remote get-url --all origin
git remote get-url --push --all origin
git ls-remote --get-url origin
Typical SSH forms are:
ssh://git@git.example.com:22/team/project.git
git-prod:team/project.git
git@github.com:OWNER/REPOSITORY.git
The alias in a scp-like URL (git-prod:...) must match the Host git-prod block. Confirm repository owner/path and whether the service expects a .git suffix. Do not mutate the remote until you have recorded the old value. If correction is required:
old_origin=$(git remote get-url origin)
printf 'old origin: %s\n' "$old_origin"
git remote set-url origin git-prod:team/project.git
git remote get-url --all origin
git remote get-url --push --all origin
Rollback is git remote set-url origin "$old_origin". If fetch and push intentionally target different locations, back up and change them separately rather than assuming origin has one URL.
Inspect the server side
This section requires an existing console, recovery channel, or another administrator session. Do not weaken SSH to recover SSH. Managed Git hosts control this layer for you; use their account/key audit tools rather than editing their filesystem.
For an OpenSSH server, confirm the requested OS account and effective daemon policy:
id USERNAME
sudo sshd -T -C user=USERNAME,addr=CLIENT_IP,host=CLIENT_HOST |
grep -E '^(pubkeyauthentication|authorizedkeysfile|strictmodes|pubkeyacceptedalgorithms) '
sudo -u USERNAME ls -ld \
/home/USERNAME \
/home/USERNAME/.ssh \
/home/USERNAME/.ssh/authorized_keys
sudo ssh-keygen -lf /home/USERNAME/.ssh/authorized_keys -E sha256
sudo journalctl --since '-10 min' -u ssh -u sshd --no-pager
Log unit names and file locations differ by operating system; use the platform’s SSH authentication log. Do not paste the whole authorized_keys file into a support request.
With default StrictModes, sshd rejects an authorized_keys path when the home directory, .ssh, or file is writable by other users. The usual .ssh and authorized_keys modes are 700 and 600, and ownership must match the login account. Before repair, back up the file through the recovery channel and verify the correct group:
id USERNAME
sudo cp -p \
/home/USERNAME/.ssh/authorized_keys \
/home/USERNAME/.ssh/authorized_keys.pre-change
sudo chown USERNAME:USERGROUP /home/USERNAME/.ssh
sudo chown USERNAME:USERGROUP /home/USERNAME/.ssh/authorized_keys
sudo chmod 700 /home/USERNAME/.ssh
sudo chmod 600 /home/USERNAME/.ssh/authorized_keys
sudo sshd -t
Do not set StrictModes no as a permission workaround. Keep the existing admin session open, validate sshd -t, reload according to the operating system, and test a separate new connection before closing the recovery session. Restore the backup if the intended key or access policy was changed incorrectly.
EC2-specific username and access checks
The EC2 SSH username is determined by the AMI, not by the AWS account name. AWS currently documents these common defaults:
| AMI family | Common default username |
|---|---|
| Amazon Linux | ec2-user |
| Ubuntu | ubuntu |
| Debian | admin |
| CentOS | centos or ec2-user |
| Fedora | fedora or ec2-user |
| RHEL/SUSE | ec2-user or root |
| Bitnami | bitnami |
Confirm the exact AMI and its provider documentation instead of trying names until one works. Also confirm the instance address, security-group route to TCP 22 (or the configured port), and that the private key corresponds to the public key provisioned for that instance.
An EC2 machine can expose two different SSH identities: an OS login such as ubuntu@host, and a Git service endpoint such as git@host. Use the one the remote URL and server design require. EC2 Instance Connect can also provide temporary public-key access; its IAM ec2:osuser condition must match the OS user. None of these workflows requires copying your private key onto the instance.
Treat algorithm errors as a separate diagnosis
Messages such as no matching host key type found, no matching key exchange method found, or no mutual signature algorithm are negotiation evidence, not generic proof that the key is absent. Inspect what the client supports and what the alias selects:
ssh -Q HostKeyAlgorithms
ssh -Q PubkeyAcceptedAlgorithms
ssh -Q kex
ssh -G git-prod |
awk '$1 ~ /^(hostkeyalgorithms|pubkeyacceptedalgorithms|kexalgorithms)$/ { print }'
HostKeyAlgorithms authenticates the server; PubkeyAcceptedAlgorithms concerns user authentication. Also, an RSA key is not synonymous with the obsolete ssh-rsa SHA-1 signature algorithm: modern RSA keys can use RSA-SHA2 when both ends support it.
The preferred repair is to update the old endpoint or provision a supported modern key. If a documented, time-bounded compatibility exception is unavoidable, scope only the directive named by the verified error to one exact alias—never put it under Host * and never enable both merely as a guess:
Host legacy-git
HostName legacy-git.example.com
User git
# Temporary only, after verifying the exact negotiation error:
# HostKeyAlgorithms +ssh-rsa
# PubkeyAcceptedAlgorithms +ssh-rsa
Record an owner and removal date. A temporary exception that becomes permanent is unresolved infrastructure debt.
Rollback and acceptance criteria
Before changing client or server files, make a uniquely named, permission-preserving backup and record the current fingerprints and remote URLs. Test client config parsing with:
ssh -G -F "$HOME/.ssh/config" git-prod >/dev/null
For server config, use sudo sshd -t before reload and retain a working recovery session. Restore one change at a time if the evidence moves backward.
The repair is accepted only when all applicable checks pass:
- The server host-key fingerprint matches an authenticated/out-of-band source.
ssh -G git-prodshows the intended hostname, port, username, agent, identity, andidentitiesonly yes.- The advertised key fingerprint matches the account/server registration, and a required agent/provider can sign without refusal.
- A fresh
ssh -vvv -o BatchMode=yes -T git-prodreaches the expected endpoint and no longer ends in host-key, public-key, or agent-refusal failure. For GitHub, its successful-authentication message is evidence even though the service provides no shell and may return a nonzero status. - The read-only Git transport test succeeds and names the expected repository:
git ls-remote --symref origin HEAD
- Only then retry the intended, reviewed push. A successful
ls-remoteproves read authentication, not necessarily write authorization or hook acceptance; do not claim the push is fixed until the actual intended write is accepted.
Primary documentation
- OpenSSH `ssh(1)`
- OpenSSH `ssh_config(5)`
- OpenSSH `ssh-add(1)`
- OpenSSH `ssh-keyscan(1)` security warning
- OpenSSH `sshd(8)` and `authorized_keys` permissions
- OpenSSH legacy algorithm guidance
- Git `remote` documentation
- Git `ls-remote` documentation
- GitHub: troubleshoot `Permission denied (publickey)`
- GitHub’s current SSH host-key fingerprints
- AWS: EC2 default Linux usernames and `authorized_keys`
- AWS: troubleshoot EC2 SSH connection errors
—
Original 2017 post (verbatim archive)
The following is the complete WordPress export body originally published under the title PERMISSION DENIED (PUBLICKEY). on 2017-05-13 and recorded as modified on 2023-09-30. Only invisible trailing spaces on the three list lines “Generate your key”, “Configure ssh to use the key”, and “Copy your key to your server” were removed for repository hygiene. All visible wording, obsolete commands, links, indentation, and omissions remain unchanged. Nothing in this archive should be treated as current authority.
A problem occurred while I `git push` to my git server on ec2.
I handled this by following this three guide of which their original links are:
http://stackoverflow.com/questions/13363553/git-error-host-key-verification-failed-when-connecting-to-remote-repository
https://chenhuachao.com/2016/05/26/ssh%E5%87%BA%E9%94%99-sign-and-send-pubkey-signing-failed-agent-refused-operation/
Sorry for missing out the second source link, I will add that later.
sign_and_send_pubkey: signing failed: agent refused operation
Permission denied (publickey).
fatal: Could not read from remote repository.
1. `mkdir ~/.ssh`
2. `vim known_hosts` – if you already have *known_hosts*, skip this.
3. `ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts`
4. `ssh-keygen -t rsa -C "user.email"`
5. Add the *id_rsa.pub* key to SSH keys list on your GitHub profile.
Table of Contents
Toggle
- [Set up your client](https://blog.lazying.art/en/html/computer_internet/git/29/permission-denied-publickey-2.html/#Set_up_your_client)
## Set up your client
1. Generate your key
- `ssh-keygen`
2. Configure ssh to use the key
- `vim ~/.ssh/config`
3. Copy your key to your server
- `ssh-copy-id -i /path/to/key.pub SERVERNAME`
Your config file from *step 2* should have something similar to the following:
Host SERVERNAME
Hostname ip-or-domain-of-server
User USERNAME
PubKeyAuthentication yes
IdentityFile ./path/to/key
eval “$(ssh-agent -s)”
ssh-add
