网页能开、SSH 却超时?——GitHub 与协会 Gitea SSH 密钥配置排障实录
本文记录一次真实的 SSH 密钥配置排障全过程。我要给 GitHub 和协会 Gitea 配好 SSH 免密推送,结果被一个「连接超时」坑了两天。所有命令和输出均为实际操作实录,排查思路和命令可以直接照搬,换成你自己的服务器地址即可。
为什么要把推送从 HTTPS 换成 SSH?
先花一分钟说清楚这件事的意义。
平时我们 git push 到 GitHub、Gitea,最常见的方式是 HTTPS:每次推送都要输一遍账号密码(或 Personal Access Token),又麻烦又不安全——密码还可能被记录在缓存里。
SSH 密钥认证是另一种方式:你本机生成一把「钥匙」(私钥,自己保管)和一把「锁」(公钥,交给平台)。推送时 Git 用私钥签名,服务器用公钥验签——全程免密,而且比密码更安全。
打个比方:HTTPS 是每次进门都要把身份证掏出来验一遍,SSH 是办了一张门禁卡,一刷就开。
配置过程本身不难,真正的难点在于,你可能会遇到一个让人摸不着头脑的报错:连接超时。
下面就是我的排障全记录。
一、背景:要给两个平台各配一把密钥
- 目标:给 GitHub 和协会 Gitea 配置 SSH 密钥认证,摆脱 HTTPS + 账号密码
- 仓库:
HealthcareAI,远程origin原为 HTTPS 地址 - 环境:Windows 11 + Git Bash,OpenSSH 10.2p1
- Gitea 服务器:
gitea.liuhangyv.top,SSH 端口是 222(不是常见的 22)
开工前先确认现状:
ls -la ~/.ssh/ # 只有 known_hosts(SSH 记录已信任服务器指纹的文件,指纹是服务器的唯一标识,类似身份证号),没有任何密钥
git config --global --get user.name # 空
git remote -v # origin 是 HTTPS 地址
好,从零开始。先给 GitHub 和 Gitea 各生成一把独立的 ed25519 密钥。ed25519 是现代标准推荐,短、快、安全;两把密钥分开生成,可分别吊销:
# Gitea 密钥
ssh-keygen -t ed25519 -C "1656747288@qq.com (gitea)" -f ~/.ssh/id_ed25519_gitea -N ""
# GitHub 密钥
ssh-keygen -t ed25519 -C "1656747288@qq.com (github)" -f ~/.ssh/id_ed25519_github -N ""
-C:注释(邮箱),用来给密钥起个备注,方便日后区分哪把是谁的-f:密钥文件名-N "":空口令,方便自动化(如果在意安全、想要口令保护,去掉-N ""交互式输入即可)- 公钥在
.pub文件里,私钥千万不要外传
二、症状:GitHub 报错、Gitea 直接超时
GitHub 先测试,得出关键经验
ssh -T -o ConnectTimeout=10 git@github.com
# 输出:git@github.com: Permission denied (publickey).
# exit 255(exit 码非 0 即连接失败,exit 255 / exit 1 都常见)
看到 Permission denied (publickey) 先别慌——这其实是个好消息。它说明网络是通的、服务器也找对了,只是当前还没有可用于认证的密钥。这是配置成功之前完全正常的现象。
Gitea 才是真正的大坑:连接超时
ssh -T -p 222 git@gitea.liuhangyv.top
# ssh: connect to host gitea.liuhangyv.top port 222: Connection timed out ← 直接超时,连报错的机会都不给
注:首次测试没加
-o ConnectTimeout,Windows 下要等完整个超时周期,可能会等上一会儿;后面的排查命令都会带上它。
ssh-keyscan(探测服务器 SSH 指纹的工具)-p 222 gitea.liuhangyv.top 也是 exit 1,stdout 无任何密钥记录。明明网页能访问,SSH 却超时——问题到底出在哪?
三、排查:用 ssh -v 看它到底在连谁
超时的原因可能有一百种:端口没开?防火墙拦截?服务器宕机?——别瞎猜,让 SSH 自己告诉你它在连哪个 IP。
ssh -T -p 222 -v git@gitea.liuhangyv.top 2>&1 | grep -E "Connecting|timed out"
# 输出(多行展示,已省略每行的 debug1: 前缀):
# Connecting to gitea.liuhangyv.top [122.228.243.52] port 222.
# connect to address 122.228.243.52 port 222: Connection timed out
# Connecting to gitea.liuhangyv.top [122.228.243.77] port 222.
# connect to address 122.228.243.77 port 222: Connection timed out
# Connecting to gitea.liuhangyv.top [122.228.243.7] port 222.
# connect to address 122.228.243.7 port 222: Connection timed out
关键发现:gitea.liuhangyv.top 这个域名在 DNS(互联网的"通讯录",负责把域名翻译成 IP)里解析出了 3 个 IP(122.228.243.x),而这 3 个 IP 的 222 端口全部不通——注意是超时(静默丢弃)而不是连接拒绝,这正是 CDN/边缘节点的标准安全设计。
可网页(HTTPS)明明能打开、服务器明显在线。这说明——域名解析到的 IP,和 Gitea SSH 服务真正监听的 IP,根本不是同一个。
而这次的原因,恰恰不是 DNS 失效,而是:这个域名接入了阿里云 ESA(Edge Security Acceleration,边缘安全加速)——它和 CDN(内容分发网络)是同类产品,都做边缘加速。DNS 解析出的 122.228.243.x 是 ESA 的边缘节点 IP,它们负责代理 HTTP/HTTPS(443/80 端口),但 SSH(222 端口)不在 ESA 的代理范围内——边缘节点上根本没有监听/转发 222 端口的服务,请求自然超时。网页能开,是因为它走的是 HTTPS,被边缘节点正常代理了。
转折点:翻协会教程,找到源站真实 IP
协会 Gitea 教程里,SSH 配置示例写死了 HostName <源站真实IP>——这正是 Gitea 所在的源站服务器真实 IP。直接拿这个 IP 测试:
ssh -p 222 -o ConnectTimeout=8 -v git@<源站真实IP> 2>&1 | grep -E "Connecting|established|Permission"
# 输出:
# Connecting to <源站真实IP> [<源站真实IP>] port 222.
# Connection established. ← 端口是通的!
# git@<源站真实IP>: Permission denied (publickey). ← 只是缺密钥
根因确认:<源站真实IP>:222 端口通!Gitea 的 SSH 服务实际监听在源站服务器的真实 IP 上。而域名 DNS 解析出的 122.228.243.x 是 ESA 边缘节点,它们只管 HTTP/HTTPS、不代理 SSH——所以走域名就超时,直连源站就通。
结论:SSH 走域名会被 ESA 边缘节点挡住,必须在
~/.ssh/config里把HostName写死成源站真实 IP(这里用<源站真实IP>占位)。
四、解法:写死 HostName
第 1 步:编写 ~/.ssh/config
# GitHub
Host github.com
HostName github.com
User git
Port 22
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
# 协会 Gitea(域名走了 ESA 边缘加速,SSH 需直连源站真实 IP)
Host gitea.liuhangyv.top
HostName <源站真实IP>
User git
Port 222
IdentityFile ~/.ssh/id_ed25519_gitea
IdentitiesOnly yes
三个关键点,缺一不可:
HostName <源站真实IP>—— 绕过 ESA 边缘节点,直连源站真实 IP(本次问题的核心解法)Port 222—— Gitea 的 SSH 用的是非默认端口,别拿 GitHub 的 22 端口思维硬套IdentitiesOnly yes—— 手上有两把密钥时,只出示该主机指定的那一把密钥,避免 SSH 逐把尝试触发Too many authentication failures
第 2 步:上传公钥到两个平台
cat ~/.ssh/id_ed25519_gitea.pub
cat ~/.ssh/id_ed25519_github.pub
- Gitea:浏览器打开
https://gitea.liuhangyv.top/user/settings/keys→ 「添加密钥」→ 粘贴公钥 → 保存 - GitHub:打开
https://github.com/settings/keys→ New SSH key → 粘贴公钥 → 保存
第 3 步:验证认证
ssh -T git@gitea.liuhangyv.top
# 输出:Hi there, hejie! You've successfully authenticated with the key named Windows,
# but Gitea does not provide shell access.
ssh -T git@github.com
# 输出:Hi hejie! You've successfully authenticated, but GitHub does not provide shell access.
认证成功后服务器会返回欢迎语,而不是进入 shell——这属于正常现象,别以为是自己配置错了。
第 4 步:仓库远程地址切换到 SSH
git remote set-url origin git@gitea.liuhangyv.top:MetaRL/HealthcareAI.git
git remote -v # 确认已切换
git ls-remote origin HEAD # 验证 SSH 拉取正常
附带:补全 git 全局身份
git config --global user.name "张贺杰"
git config --global user.email "1656747288@qq.com"
五、验证结果汇总
| 检查项 | 命令 | 结果 |
|---|---|---|
| Gitea 认证 | ssh -T git@gitea.liuhangyv.top | ✅ Hi there, hejie! |
| GitHub 认证 | ssh -T git@github.com | ✅ 认证成功 |
| 远程已切 SSH | git remote -v | ✅ git@gitea.liuhangyv.top:... |
| SSH 拉取 | git ls-remote origin HEAD | ✅ 返回 HEAD 提交号 |
| 密钥文件 | ls ~/.ssh/ | ✅ 两把 ed25519 密钥就位 |
六、避坑清单
- 「连接超时」先怀疑「流量真的被代理了吗」,再用
ssh -v看它在连哪个 IP(思路与证据详见第三节)。网页能开不代表 SSH 也能通。 Permission denied (publickey)其实是个好消息。 它说明网络通、服务器对,只差上传密钥。- 边缘加速只代理网页(HTTP/HTTPS):SSH 要直连源站、在
~/.ssh/config写死HostName(详见第三节)。 - 多平台密钥务必分开,用
IdentitiesOnly yes防止互扰;每把密钥可独立吊销。 - Gitea 非默认端口 222,别拿 GitHub 的 22 端口思维硬套。
- 首次连接任何 SSH 服务器都会弹指纹确认,输入
yes后该服务器指纹会记录进known_hosts,后续连接不再弹确认。 - 写死源站 IP 只是应急解:源站 IP 变更后配置会失效,把 IP 写进教程也等于暴露了绕过 CDN 的直达地址。更稳健的长期方案是给 Gitea 单独解析一个不经 ESA 的 SSH 专用子域名(如
ssh.gitea.liuhangyv.top,A 记录直指源站),配置里用这个域名而非裸 IP。
小结
这次排障教会我一件事:眼见不一定为实——「网页能开」不代表「所有流量都通」,「看起来失败」也不一定是坏事。别被表象带着走,让 ssh -v 告诉你它到底在连谁。
希望这篇实录能帮你少踩一次坑。下次给协会 Gitea 配 SSH,记得先看一眼 ~/.ssh/config 里的 HostName 是不是直连了源站 😄
评论交流
欢迎留下你的想法