Git SSH“Permission denied (publickey)”:循证故障排查(2026)

维护指南,核查于 2026-09-01。 2017 年原文并非空白,完整存档在文末。旧文中的历史命令——包括未经验证就追加 ssh-keyscan 输出——只是存档材料,不是当前操作建议。下面先把主机身份、客户端认证、agent 签名、Git 路由和服务器授权分开,再决定是否修改。

先判断故障属于哪一层

同时出现的三条消息可能分别来自不同层:

调试输出中的证据层次含义
REMOTE HOST IDENTIFICATION HAS CHANGEDHost key verification failed服务器身份客户端尚未建立对服务器密钥的信任;此时不要先排查用户密钥。
Permission denied (publickey)用户认证服务器没有为请求的用户名完成公钥认证;原因不一定是“没有密钥”。
sign_and_send_pubkey: ... agent refused operation客户端签名SSH 已找到或提交某个身份,但 agent/提供器拒绝执行签名;重新安装公钥未必有用。
fatal: Could not read from remote repositoryGit 汇总Git 只说明 SSH 传输失败,并未指出是哪一层失败。

不要同时删除 known_hosts、重建所有密钥并修改服务器权限。这样既会毁掉证据,也可能把一个故障变成多个。

从只读取证开始

请在实际运行 Git 的同一账户、终端、容器、IDE 或 CI 任务中执行。sudo git push 可能选择另一套主目录、配置和 agent,因此不要提权。

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=%sn' "${SSH_AUTH_SOCK:-<unset>}"
if [ -n "${SSH_AUTH_SOCK:-}" ] && [ -S "$SSH_AUTH_SOCK" ]; then
  printf '%sn' 'agent socket exists'
else
  printf '%sn' 'agent socket is absent or not a socket'
fi

git-prod 替换为远程地址中真正使用的 SSH 主机或别名。ssh -G 输出生效后的客户端配置并退出。ssh -vvv ... BatchMode=yes 会尝试认证,但不显示密码或主机确认提示,也不会推送 Git 数据。对于普通 shell 服务器,-T 仍可能打开无终端会话;若服务器不自行退出,在取得诊断输出后停止它。

调试日志可能包含账户名、主机名、IP 地址和本地路径。分享前应遮盖这些内容,但保留算法名称、指纹以及 Offering public keyServer accepts key 和报错的先后次序。

按顺序阅读详细日志

  1. 目的地: 核对 Connecting to ...、端口以及 Authenticating to ... as ...。即使密钥完全正确,连错主机或用户名仍会失败。
  2. 主机验证: 找到选中的主机密钥算法和指纹。主机密钥错误发生在用户认证之前。
  3. 身份发现: identity file ... type -1 表示该路径没有找到文件;Offering public key 才说明实际提交了什么。
  4. 服务器响应: 提交后仍反复出现 Authentications that can continue: publickey,通常说明服务器不接受该用户名/密钥组合或策略。
  5. 签名:Server accepts key 之后紧跟 sign_and_send_pubkey: ... agent refused operation,应排查被选中的 agent、密钥约束、解锁/确认状态或硬件提供器。
  6. 仓库访问: 只有 SSH 认证成功后,才应继续排查 Git 仓库路径和授权。

保存带客户端版本和时间的脱敏日志。不要把另一台机器上复制的一行输出当作当前会话的证据。

安全验证主机密钥

ssh-keyscan 只取得网络端点当前展示的密钥,不会认证该密钥。OpenSSH 明确警告:用未经验证的扫描结果构造 known_hosts,会让用户暴露于中间人攻击。

先通过已认证或带外渠道取得预期的 SHA-256 指纹:登录后的服务商控制台或官方文档、通过另一条已核验渠道联系服务器管理员,或直接使用服务器控制台。随后只比较,不修改 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

使用非标准端口时,known-hosts 名称通常是 [git.example.com]:2222。对于 GitHub,请与 GitHub 当前官方指纹页比较;不要相信论坛或本文中复制的指纹。

上面的 ed25519 扫描只是示例,并非兼容性保证。若已认证来源发布的是另一种主机密钥类型,应取得并比较服务器实际提供的那一种,不要靠猜测削弱客户端策略。

只有带外指纹完全匹配后,才备份并修改记录:

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"

若已保存的密钥发生变化,先调查是否为正常轮换或入侵。验证并备份后,仅用 ssh-keygen -R git.example.com(或带方括号的主机与端口)删除那条旧记录,再加入核验过的新记录。绝不要用 StrictHostKeyChecking no、清空 known_hosts 或盲目追加扫描结果来“解决”。

检查身份和 agent

只查看指纹,不输出私密材料:

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"

把公钥指纹与 Git 账户中登记的密钥,或目标用户 authorized_keys 中的密钥比较。绝不要把私钥复制到服务器、工单、聊天、仓库或共享目录。服务器只需要单行公钥。

在支持该选项的 OpenSSH 版本中,可以测试 agent 是否真的能用对应公钥签名:

ssh-add -T "$HOME/.ssh/id_ed25519_git_prod.pub"

ssh-add -l 能列出密钥,只证明 agent 声称持有它。签名测试或真实 SSH 仍可能因以下原因被拒绝:

  • 桌面钥匙串、密码管理器或托管 agent 尚未解锁;
  • 身份要求每次确认,但当前环境没有可用提示窗口;
  • FIDO/安全密钥未插入、被锁定或等待触摸/PIN;
  • SSH_AUTH_SOCK 指向过期或错误的 agent;
  • 生效配置里的 IdentityAgent 覆盖了 SSH_AUTH_SOCK
  • 被转发或带目的地约束的 agent 拒绝这条路径;
  • 提供器不支持请求的签名操作。

请在拥有该身份的 agent/提供器里解锁或确认,然后重试。不要一开始就删除所有身份或杀死桌面 agent。若确实没有托管 agent,并且只想做一次隔离 shell 测试,应明确它的生命周期:

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

这不是硬件保护或组织托管身份的正确修复;应修复对应提供器,或选择它的正确 socket。

固定精确的主机、用户和密钥

使用专用别名,不要写成宽泛的 Host * 例外:

Host git-prod
  HostName git.example.com
  User git
  Port 22
  IdentityFile ~/.ssh/id_ed25519_git_prod
  IdentitiesOnly yes
  PubkeyAuthentication yes

IdentitiesOnly yes 会让 OpenSSH 使用已配置的身份,而不是把 agent 提供的大量其他身份都尝试一遍。核对结果:

ssh -G git-prod |
  awk '$1 ~ /^(hostname|user|port|identityfile|identitiesonly|identityagent)$/ { print }'

GitHub 的 SSH 用户名是 git,不是 GitHub 个人资料名。自托管 Git 服务也可能要求 git,而直接登录 EC2 则使用操作系统账户。不要把某个服务的用户名套到另一个服务。

客户端文件不能被其他用户写入;私钥也不能被其他用户读取。保留备份并确认目标后,以下是保守权限:

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"

对于仅存在于 agent 或硬件中的身份,IdentityFile 可以指向公钥文件,以选择 agent 内对应的私钥。具体遵循提供器的官方设置;硬件密钥存根也可能就是配置的身份文件。不要为了模仿本例文件布局而导出私钥。

只在确有需要时生成新密钥

不要覆盖仍有效的身份。客户端、服务器和提供器支持时,Ed25519 是现代默认选择;环境兼容时,FIDO Ed25519 还能提供硬件保护。使用独立文件名,并设置适合使用环境的口令:

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

对于兼容的 FIDO 硬件和服务器:

umask 077
ssh-keygen -t ed25519-sk 
  -f "$HOME/.ssh/id_ed25519_sk_git_prod" 
  -C "git-prod hardware key"

只能通过服务的已认证密钥管理页面或管理员的受控流程登记 .pub 内容。新密钥验证成功前保留旧密钥,随后再有计划地撤销。新密钥不能修复未验证的主机密钥、错误用户名、错误远程地址或损坏的 agent socket。

核对 Git 远程地址

Git 可以分别配置 fetch 和 push URL,insteadOfpushInsteadOf 重写也可能改变实际目的地。把它们全部检查出来:

git remote -v
git remote get-url --all origin
git remote get-url --push --all origin
git ls-remote --get-url origin

常见 SSH 格式如下:

ssh://git@git.example.com:22/team/project.git
git-prod:team/project.git
git@github.com:OWNER/REPOSITORY.git

类 scp URL 中的别名(git-prod:...)必须对应 Host git-prod 配置块。核对仓库所有者/路径,以及服务是否要求 .git 后缀。记录旧值之前不要修改。确需修正时:

old_origin=$(git remote get-url origin)
printf 'old origin: %sn' "$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

回滚命令是 git remote set-url origin "$old_origin"。若 fetch 与 push 有意指向不同位置,应分别备份和修改,不要假设 origin 只有一个 URL。

检查服务器端

本节要求已有控制台、恢复通道或另一个管理员会话。不要通过削弱 SSH 来恢复 SSH。托管 Git 平台会替你管理这一层,应使用其账户/密钥审计功能,而不是修改其文件系统。

对于 OpenSSH 服务器,核对请求的操作系统账户和生效的 daemon 策略:

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

日志单元名和文件位置因操作系统而异,请使用平台的 SSH 认证日志。不要把整个 authorized_keys 粘贴到支持工单。

默认 StrictModes 下,如果家目录、.sshauthorized_keys 可被其他用户写入,sshd 会拒绝使用它。常见权限是 .ssh700authorized_keys600,所有者必须与登录账户一致。修复前,先通过恢复通道备份文件并确认正确的用户组:

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

不要把 StrictModes no 当作权限修复。保留现有管理员会话,先通过 sshd -t,按操作系统方式 reload,并在关闭恢复会话前另开一个连接测试。若误改了预期密钥或访问策略,恢复备份。

EC2 特有的用户名和访问检查

EC2 的 SSH 用户名由 AMI 决定,不是 AWS 账户名。AWS 当前列出的常见默认值如下:

AMI 系列常见默认用户名
Amazon Linuxec2-user
Ubuntuubuntu
Debianadmin
CentOScentosec2-user
Fedorafedoraec2-user
RHEL/SUSEec2-userroot
Bitnamibitnami

先确认精确 AMI 和提供商文档,不要逐个猜用户名。还要确认实例地址、安全组是否允许到 TCP 22(或配置端口)的路径,以及私钥是否对应为该实例预置的公钥。

一台 EC2 主机可能提供两种 SSH 身份:例如 ubuntu@host 的操作系统登录,以及 git@host 的 Git 服务端点。应按远程 URL 和服务器设计选择。EC2 Instance Connect 也能提供临时公钥访问;其 IAM ec2:osuser 条件必须匹配操作系统用户。所有这些流程都不需要把私钥复制到实例。

把算法错误单独诊断

no matching host key type foundno matching key exchange method foundno mutual signature algorithm 是协商证据,不是“缺少密钥”的泛化证明。检查客户端支持的算法以及别名最终选择:

ssh -Q HostKeyAlgorithms
ssh -Q PubkeyAcceptedAlgorithms
ssh -Q kex

ssh -G git-prod |
  awk '$1 ~ /^(hostkeyalgorithms|pubkeyacceptedalgorithms|kexalgorithms)$/ { print }'

HostKeyAlgorithms 用于认证服务器,PubkeyAcceptedAlgorithms 涉及用户认证。RSA 密钥也不等于过时的 ssh-rsa SHA-1 签名算法:两端支持时,现代 RSA 密钥可以使用 RSA-SHA2。

首选修复是升级旧端点,或配置受支持的现代密钥。若确实无法避免有文档、限时的兼容例外,只把经验证报错明确指出的那一条指令限定到一个精确别名;绝不要放在 Host * 下,也不要凭猜测同时启用两项:

Host legacy-git
  HostName legacy-git.example.com
  User git
  # Temporary only, after verifying the exact negotiation error:
  # HostKeyAlgorithms +ssh-rsa
  # PubkeyAcceptedAlgorithms +ssh-rsa

为例外记录负责人和移除日期。永久存在的“临时例外”就是尚未解决的基础设施债务。

回滚与验收标准

修改客户端或服务器文件前,创建保留权限且名称唯一的备份,并记录当前指纹和远程 URL。用下面命令测试客户端配置解析:

ssh -G -F "$HOME/.ssh/config" git-prod >/dev/null

服务器配置应在 reload 前执行 sudo sshd -t,并保留可用的恢复会话。证据变差时,每次只回滚一项。

只有所有适用条件都满足,修复才算验收:

  1. 服务器主机密钥指纹与已认证/带外来源一致。
  2. ssh -G git-prod 显示预期的主机名、端口、用户名、agent、身份以及 identitiesonly yes
  3. agent 公布的密钥指纹与账户/服务器登记一致,并且所需 agent/提供器能够签名且不再拒绝。
  4. 新执行的 ssh -vvv -o BatchMode=yes -T git-prod 到达预期端点,最终不再出现主机密钥、公钥或 agent 拒绝故障。对于 GitHub,即使服务不提供 shell 且可能返回非零状态,其认证成功消息仍是有效证据。
  5. 只读 Git 传输测试成功,并指向预期仓库:
git ls-remote --symref origin HEAD
  1. 这之后才重试经过审阅的目标 push。ls-remote 成功只证明读认证,不保证写授权或服务端 hook 接受;实际目标写入被接受前,不要宣称 push 已修复。

一手官方文档

---

2017 英文原文(逐字存档)

以下是最初以 PERMISSION DENIED (PUBLICKEY). 为题、于 2017-05-13 发布,且导出元数据显示 2023-09-30 修改的 WordPress 正文全文。仅为仓库整洁移除了三条列表项“Generate your key”“Configure ssh to use the key”“Copy your key to your server”行末不可见的空格;所有可见字词、旧命令、链接、缩进和缺漏均保持原样。该存档中的内容不应被当作当前权威指南。


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

Leave a Reply