维护指南,核查于 2026-09-01。 2017 年原文并非空白,完整存档在文末。旧文中的历史命令——包括未经验证就追加
ssh-keyscan输出——只是存档材料,不是当前操作建议。下面先把主机身份、客户端认证、agent 签名、Git 路由和服务器授权分开,再决定是否修改。
Table of Contents
先判断故障属于哪一层
同时出现的三条消息可能分别来自不同层:
| 调试输出中的证据 | 层次 | 含义 |
|---|---|---|
REMOTE HOST IDENTIFICATION HAS CHANGED 或 Host key verification failed | 服务器身份 | 客户端尚未建立对服务器密钥的信任;此时不要先排查用户密钥。 |
Permission denied (publickey) | 用户认证 | 服务器没有为请求的用户名完成公钥认证;原因不一定是“没有密钥”。 |
sign_and_send_pubkey: ... agent refused operation | 客户端签名 | SSH 已找到或提交某个身份,但 agent/提供器拒绝执行签名;重新安装公钥未必有用。 |
fatal: Could not read from remote repository | Git 汇总 | 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 key、Server accepts key 和报错的先后次序。
按顺序阅读详细日志
- 目的地: 核对
Connecting to ...、端口以及Authenticating to ... as ...。即使密钥完全正确,连错主机或用户名仍会失败。 - 主机验证: 找到选中的主机密钥算法和指纹。主机密钥错误发生在用户认证之前。
- 身份发现:
identity file ... type -1表示该路径没有找到文件;Offering public key才说明实际提交了什么。 - 服务器响应: 提交后仍反复出现
Authentications that can continue: publickey,通常说明服务器不接受该用户名/密钥组合或策略。 - 签名: 若
Server accepts key之后紧跟sign_and_send_pubkey: ... agent refused operation,应排查被选中的 agent、密钥约束、解锁/确认状态或硬件提供器。 - 仓库访问: 只有 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,insteadOf/pushInsteadOf 重写也可能改变实际目的地。把它们全部检查出来:
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 下,如果家目录、.ssh 或 authorized_keys 可被其他用户写入,sshd 会拒绝使用它。常见权限是 .ssh 为 700、authorized_keys 为 600,所有者必须与登录账户一致。修复前,先通过恢复通道备份文件并确认正确的用户组:
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 Linux | ec2-user |
| Ubuntu | ubuntu |
| Debian | admin |
| CentOS | centos 或 ec2-user |
| Fedora | fedora 或 ec2-user |
| RHEL/SUSE | ec2-user 或 root |
| Bitnami | bitnami |
先确认精确 AMI 和提供商文档,不要逐个猜用户名。还要确认实例地址、安全组是否允许到 TCP 22(或配置端口)的路径,以及私钥是否对应为该实例预置的公钥。
一台 EC2 主机可能提供两种 SSH 身份:例如 ubuntu@host 的操作系统登录,以及 git@host 的 Git 服务端点。应按远程 URL 和服务器设计选择。EC2 Instance Connect 也能提供临时公钥访问;其 IAM ec2:osuser 条件必须匹配操作系统用户。所有这些流程都不需要把私钥复制到实例。
把算法错误单独诊断
no matching host key type found、no matching key exchange method found 或 no 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,并保留可用的恢复会话。证据变差时,每次只回滚一项。
只有所有适用条件都满足,修复才算验收:
- 服务器主机密钥指纹与已认证/带外来源一致。
ssh -G git-prod显示预期的主机名、端口、用户名、agent、身份以及identitiesonly yes。- agent 公布的密钥指纹与账户/服务器登记一致,并且所需 agent/提供器能够签名且不再拒绝。
- 新执行的
ssh -vvv -o BatchMode=yes -T git-prod到达预期端点,最终不再出现主机密钥、公钥或 agent 拒绝故障。对于 GitHub,即使服务不提供 shell 且可能返回非零状态,其认证成功消息仍是有效证据。 - 只读 Git 传输测试成功,并指向预期仓库:
git ls-remote --symref origin HEAD
- 这之后才重试经过审阅的目标 push。
ls-remote成功只证明读认证,不保证写授权或服务端 hook 接受;实际目标写入被接受前,不要宣称 push 已修复。
一手官方文档
- OpenSSH `ssh(1)`
- OpenSSH `ssh_config(5)`
- OpenSSH `ssh-add(1)`
- OpenSSH `ssh-keyscan(1)` 安全警告
- OpenSSH `sshd(8)` 与 `authorized_keys` 权限
- OpenSSH 旧算法指南
- Git `remote` 文档
- Git `ls-remote` 文档
- GitHub:排查 `Permission denied (publickey)`
- GitHub 当前 SSH 主机密钥指纹
- AWS:EC2 默认 Linux 用户名与 `authorized_keys`
- AWS:排查 EC2 SSH 连接错误
---
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
