DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

ssh 测试成功,git push 却 Permission denied:检查主机与密钥配置

用网友 ssh -T 成功但推送失败的案例,比较 github.com 与 ssh.github.com、remote URL、Git SSH 覆盖设置和实际提供的密钥。

ssh -T git@github.com 已能显示认证成功,git push 却报 Permission denied (publickey)。先比较 Git 实际访问的主机、执行的 SSH 命令和提供的密钥。单独的 SSH 测试通过,只证明那次调用成功,不保证 Git 使用了相同配置。

Windows 上尤其要比较 Git 调用的 SSH 与独立调用的可执行文件、配置路径和 agent。认证用户名多了字符、Git 自带 SSH 与系统 SSH 使用不同设置,都可能导致两次调用结果不同,不必先重装 Git。

测试使用 github.com,推送却连接 ssh.github.com 时,要继续比较两次调用的展开配置和密钥选择。即使两个主机的独立测试都曾成功,也不能证明 Git 使用了相同配置。

先看 remote 的读取和推送地址

在报错的仓库目录查看:

git remote -v
git config --show-origin --get core.sshCommand
git config --show-origin --get-regexp '^url\..*\.insteadof$'

git remote -v 分别列出读取和推送地址,两者可能不同。SSH URL 通常使用 git 这个用户,不是你的 GitHub 用户名。仓库地址属于哪个主机、是否有 host 别名、是否设置了单独的推送 URL,都要以这里的实际值为准。

后两条查询检查 Git 是否指定了 SSH 命令,或把 URL 改写为另一个地址。--show-origin 可以帮助找到设置来自哪个配置文件。没有匹配项时,这些查询可能以非零退出码结束,并不等于仓库损坏。另外检查启动 Git 的环境是否设置了 GIT_SSH_COMMAND、GIT_SSH;它们可能覆盖你平时在终端里调用的 SSH。

如果确认 remote 写错了,可以将它修改为实际仓库的地址,但修改前记录原值,尤其注意专门设置的 push URL。不要为了尝试而删除所有全局 Git 配置。

github.com 和 ssh.github.com 都可能是正确配置

GitHub 官方支持在 ssh.github.com 的 443 端口上使用 SSH,用于某些阻止 22 端口的网络。它不是 HTTPS 协议认证,也不是所有错误日志里出现它就说明设置错了。

对于 OpenSSH,可以先查看主机最终展开的配置:

ssh -G github.com
ssh-add -l

ssh -G 不建立远程连接,关注其中的 hostname、port、user 和 identityfile。如果 remote 使用的是别名,应查看那个别名,而不是仍查询 github.com。ssh-add -l 列出当前 agent 中的密钥指纹;agent 没有密钥不代表指定的身份文件一定不能使用,还需查看实际认证过程。

SSH 配置可以把 github.com 映射到 ssh.github.com:443,因此即使 remote 字面上包含 github.com,最终连接也可能走另一个主机与端口。只改 remote 的文字,不一定能改变 SSH 展开后的结果。

比较 Git 实际提供了哪把密钥

如果这个仓库本来使用当前系统的 OpenSSH,可以用一次只读远程查询取得调试信息:

GIT_SSH_COMMAND='ssh -v' git ls-remote origin

它读取远程引用,不提交或推送代码。这个环境变量只针对当前命令,并会覆盖已有的 SSH 命令选择;若仓库故意使用特定可执行文件或额外选项,应在保留它们的前提下加调试,而不是用这条命令假装验证了原配置。

比较独立 SSH 测试与 Git 调用中的连接主机、端口、身份文件和 Offering public key 行。需要的是被服务器接受的正确身份,而不是终端里“有一把密钥”。日志显示公钥已被尝试、认证却未完成时,应继续检查密钥选择,不能据此认定 GitHub 服务宕机。

Windows 上还要注意 Git 自带 OpenSSH、系统 OpenSSH 或其他 SSH 客户端可能读取不同配置、访问不同 agent。路径中出现 /c/Users/... 可以是 MSYS 的正常写法,本身不证明 SSH 程序损坏。IDE 也可能通过自己的环境启动 Git。终端成功而 IDE 失败时,应比较这两条调用路径。

调试输出可能暴露本机路径、用户名、仓库名和网络信息,分享前脱敏。私钥文件不要上传;GitHub 账号中配置的是对应公钥。

指定身份文件时,保留已确认的网络路线

如果你确认当前网络可用 22 端口,也确认应使用 ~/.ssh/id_ed25519 这把已有私钥,OpenSSH 配置可以采用以下片段:

Host github.com
    HostName github.com
    Port 22
    User git
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

如果网络确实需要 443 端口,则保留 Host github.com 这个别名,将 HostName 设置为 ssh.github.com、Port 设置为 443,再验证这条路线。选择路线应依据实际网络和展开配置,不能通过随意删除某个主机名来试错。

IdentityFile 指向已有私钥文件;不要把 .pub 文件当作这个例子的私钥。IdentitiesOnly yes 限制额外身份的尝试,但不会把没有权限的密钥变成有权限的密钥。确认对应公钥已经加入正确 GitHub 账号,或作为符合用途的 deploy key;读取权限不自动等于写入权限。

编辑前备份 ~/.ssh/config,不要覆盖其中其他主机。已有多个 GitHub 账号时,通常需要不同 host 别名与密钥,并让各仓库 remote 使用对应别名。配置顺序和通配规则会影响最终选项,修改后再次查看展开配置;需要回退时恢复备份。

首次连接新主机或端口时,应按 GitHub 官方说明核对主机指纹,不能把关闭主机验证作为解决 publickey 错误的步骤。

认证成功后,再检查仓库权限

GitHub 的 SSH 测试成功会提示认证账号和不提供 shell 的信息;这类测试应以退出码 1 结束,不能只按退出码判为认证失败。确认显示的账号正是应该访问仓库的账号。

随后验证实际 remote 的读取,再执行原本有权限的推送操作。如果 SSH 已认证,但出现仓库不存在或无写入权限,应查仓库地址、账号授权、deploy key 是否只读以及组织的认证要求。此时继续生成新密钥或反复重启 agent,已经不能替代仓库权限检查。