能连上 GitHub(SSH 验证成功),却 push 失败?常见原因与逐步解决方案
在无法通过 HTTPS 或普通 Git 方式推送代码到远程仓库时,很多人会尝试改用 SSH 密钥的方式来连接与推送。本文先说明为什么网络策略或端口限制会导致无法 push、以及在什么场景下使用 SSH(尤其是将连接改为 ssh.github.com:443)可以绕过这些限制;随后分析为何有时即便 SSH 验证成功,git push 仍然失败,并给出可直接执行的解决方案与诊断命令,便于快速恢复推送能力。
一、为什么会出现"能连上但无法 push"的矛盾?
在继续分析具体原因之前,先简要说明"为什么有时改用 SSH 密钥可以绕过网络限制":
- 端口策略:很多内网/防火墙会屏蔽外部 SSH 的默认端口 22,但允许 443(HTTPS)端口通过。将 SSH 连接指向 ssh.github.com:443,可以利用被允许的 443 端口建立 SSH 连接,从而避开对 22 端口的阻断。
- 协议与拦截:公司代理/中间人通常针对 HTTP/HTTPS 做流量检查或凭证劫持。SSH 使用不同的协议和密钥认证,代理通常不对 SSH 做相同拦截,因此 SSH key 的认证流更难被代理截获或替换。
- 认证方式:使用密钥认证避免了在通过代理的 HTTPS 基于表单/密码的交互,从而减少凭证被代理处理或缓存的风险。 常见场景与原因总结:
- 网络端口与代理策略差异
- 许多公司封锁外部 22 端口,允许 443(HTTPS)。通过把 ssh 目标指向 ssh.github.com:443 可以通过防火墙,但这需要 SSH 客户端实际使用该端口。
- SSH 验证成功只是说明公钥在 GitHub 上被接受,但 git push 失败可能是因为:
- Git 在执行 push 时没有使用你测试时用的同一把私钥(例如你用
ssh -i
测试成功,但 git 调用的 ssh 没有读取该 key)。 - Git 调用了不同的 ssh 可执行文件(系统自带的、Git for Windows 自带的或 Putty/plink),而这些客户端对
~/.ssh/config
或端口声明的支持不同。 - remote URL 使用了
ssh://...:443
这类带端口的写法时,有的 Git 的 ssh 变体("simple")不支持在 URL 中带端口,会报 "ssh variant 'simple' does not support setting port"。 - ssh-agent 未加载或私钥权限不当,导致 push 时没有合适的私钥被提供给远端(即使单次 ssh -i 可用)。
- Git 在执行 push 时没有使用你测试时用的同一把私钥(例如你用
- 本地路径/配置格式不兼容(Windows 路径在 Git Bash 下需用 POSIX 形式),导致 Git 调用 ssh 时失败或不读取
config
。
结论:ssh 验证成功说明"密钥对 + GitHub"是正确的;失败通常出在"git 调用 ssh 的方式"或"remote URL 与客户端组合"的不匹配上。
二、优先的检查与调试命令(先执行这些,便于定位)
在能访问命令行的机器上按顺序执行(把输出保存/粘贴用于进一步分析):
-
验证 SSH 单次连接(指定私钥和端口)
-
Git Bash:
bashssh -i /c/Users/youruser/.ssh/id_ed25519_project -T -p 443 git@ssh.github.com
-
PowerShell:
powershellssh -i C:\Users\youruser\.ssh\id_ed25519_project -T -p 443 git@ssh.github.com
-
-
查看尝试密钥的详细信息(verbose)
bashssh -vvv -i /c/Users/youruser/.ssh/id_ed25519_project -T -p 443 git@ssh.github.com
-
检查 Git 用哪个 ssh(可能为空)
powershellgit config --get core.sshCommand git config --global --get core.sshCommand git var GIT_SSH where.exe ssh
-
查看远程 URL 与当前 remote
bashgit remote -v
-
在 push 时用 verbose 查看 ssh 行为(临时,不持久)
bash# Git Bash 临时 export GIT_SSH_COMMAND="ssh -vvv -F /c/Users/youruser/.ssh/config" git push origin master unset GIT_SSH_COMMAND # PowerShell 临时 $env:GIT_SSH_COMMAND = "ssh -vvv -F C:\Users\youruser\.ssh\config" git push origin master Remove-Item Env:\GIT_SSH_COMMAND
三、逐条可执行方案(按便利性/长期性排序)
方案 1 --- 推荐:在 ~/.ssh/config
映射 host(一次配置,透明生效)
-
在
C:\Users\<user>\.ssh\config
写:bashHost github.com HostName ssh.github.com Port 443 User git IdentityFile C:/Users/youruser/.ssh/id_ed25519_project IdentitiesOnly yes
-
优点:你可以继续使用
git@github.com:OWNER/REPO.git
形式的 remote,SSH 会透明走 443 并用指定 key,适合大量仓库。
方案 2 --- 推荐备选:在当前会话临时指定 ssh(立刻生效,适合不想改配置时)
-
Git Bash:
bashexport GIT_SSH_COMMAND="/c/Windows/System32/OpenSSH/ssh.exe -F /c/Users/youruser/.ssh/config" git push origin master unset GIT_SSH_COMMAND
-
PowerShell:
powershell$env:GIT_SSH_COMMAND='C:\Windows\System32\OpenSSH\ssh.exe -F C:\Users\youruser\.ssh\config' git push origin master Remove-Item Env:\GIT_SSH_COMMAND
-
优点:无需改全局设置或每个仓库,适合临时使用或测试。
方案 3 --- 持久化:让 Git 永久使用指定 OpenSSH(写入 core.sshCommand)
-
在 Git Bash(或 PowerShell)执行:
bashgit config --global core.sshCommand "/c/Windows/System32/OpenSSH/ssh.exe -F /c/Users/youruser/.ssh/config"
-
优点:一次配置后所有仓库自动使用该 ssh 客户端,适合你最终希望长期生效的场景。
方案 4 --- 把 remote 改成不带端口的 SSH(仅当前仓库)
-
在仓库目录执行:
bashgit remote set-url origin git@github.com:OWNER/REPO.git git push origin master
-
说明:如果方案 1 的
~/.ssh/config
已正确设置,这种写法会通过 config 映射到ssh.github.com:443
。适合逐仓切换的做法。
方案 5 --- 全局 URL 重写(大量仓库且 remote 多为 HTTPS)
-
让
https://github.com/...
自动走ssh://git@ssh.github.com:443/...
:bashgit config --global url."ssh://git@ssh.github.com:443/".insteadOf "https://github.com/"
-
优点:不需改每个仓库的 remote,适合公司内大量已有 HTTPS remote 的情形。
方案 6 --- 离线/替代推送(当所有外网被限制)
-
使用
git bundle
在内网打包,然后在能上网的机器上 clone/bundle 并 push:bash# 内网机器 git bundle create ../repo.bundle --all # 在能上网的机器上 git clone repo.bundle repo-from-bundle cd repo-from-bundle git remote add origin https://github.com/OWNER/REPO.git git push origin --all git push origin --tags
四、常见错误与针对性修复
-
错误 "ssh variant 'simple' does not support setting port"
- 原因:remote 使用了
ssh://...:PORT
形式,但 Git 内置 ssh 变体不支持 URL 里显式端口。 - 解决:去掉 URL 中的端口,改用
git@github.com:owner/repo.git
并依赖~/.ssh/config
做端口映射,或设置core.sshCommand
强制使用系统 OpenSSH。
- 原因:remote 使用了
-
报 "Permission denied (publickey)." 在 push 时,但
ssh -i ...
验证成功- 原因:git push 调用了不同的 ssh(或未加载该私钥),或者 ssh 在 push 时未读取你的
config
。 - 解决:用
GIT_SSH_COMMAND
指定同一 ssh 可执行文件或在~/.ssh/config
中声明IdentityFile
;也可把私钥放到默认位置并用 ssh-agent 加载。
- 原因:git push 调用了不同的 ssh(或未加载该私钥),或者 ssh 在 push 时未读取你的
-
在 Windows 下设置
core.sshCommand
出现 "command not found" 或路径问题- 原因:Git Bash 解释 Windows 路径不兼容(需要 POSIX 路径 /c/Windows/...)。
- 解决:在 Git Bash 使用
/c/Windows/System32/OpenSSH/ssh.exe
形式;在 PowerShell 使用双反斜杠转义路径。
五、安全与运维建议
- 私钥保护:私钥必须保密,放到
~/.ssh/
并限制访问权限;若私钥可能泄露,立即在 GitHub 上删除对应公钥并重新生成。 - 不要把私钥或含密信息提交到仓库。
- 若你在公司内重复遇到网络限制,建议和网管沟通开放特定目标(例如允许
ssh.github.com:443
),或申请企业级代理支持。 - 对于长期维护,优先把常用私钥加入 ssh-agent(或 Windows OpenSSH agent),并把 agent 设置为随系统可用,避免每次都输入/设置。
六、快速决策指南(选择一个最快恢复办法)
- 想最快解决并不改任何仓库:在当前终端临时执行 GIT_SSH_COMMAND(方案 2)。
- 想一次性对所有仓库透明生效:写
~/.ssh/config
并把 remote 保持git@github.com:...
(方案 1)。 - 仓库多且为 HTTPS:使用全局
insteadOf
自动重写(方案 5)。 - 完全无外网访问:使用
git bundle
离线转发(方案 6)。