网页能开、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.topSSH 端口是 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

三个关键点,缺一不可:

  1. HostName <源站真实IP> —— 绕过 ESA 边缘节点,直连源站真实 IP(本次问题的核心解法
  2. Port 222 —— Gitea 的 SSH 用的是非默认端口,别拿 GitHub 的 22 端口思维硬套
  3. 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.topHi there, hejie!
GitHub 认证ssh -T git@github.com✅ 认证成功
远程已切 SSHgit remote -vgit@gitea.liuhangyv.top:...
SSH 拉取git ls-remote origin HEAD✅ 返回 HEAD 提交号
密钥文件ls ~/.ssh/✅ 两把 ed25519 密钥就位

六、避坑清单

  1. 「连接超时」先怀疑「流量真的被代理了吗」,再用 ssh -v 看它在连哪个 IP(思路与证据详见第三节)。网页能开不代表 SSH 也能通。
  2. Permission denied (publickey) 其实是个好消息。 它说明网络通、服务器对,只差上传密钥。
  3. 边缘加速只代理网页(HTTP/HTTPS):SSH 要直连源站、在 ~/.ssh/config 写死 HostName(详见第三节)。
  4. 多平台密钥务必分开,用 IdentitiesOnly yes 防止互扰;每把密钥可独立吊销。
  5. Gitea 非默认端口 222,别拿 GitHub 的 22 端口思维硬套。
  6. 首次连接任何 SSH 服务器都会弹指纹确认,输入 yes 后该服务器指纹会记录进 known_hosts,后续连接不再弹确认。
  7. 写死源站 IP 只是应急解:源站 IP 变更后配置会失效,把 IP 写进教程也等于暴露了绕过 CDN 的直达地址。更稳健的长期方案是给 Gitea 单独解析一个不经 ESA 的 SSH 专用子域名(如 ssh.gitea.liuhangyv.top,A 记录直指源站),配置里用这个域名而非裸 IP。

小结

这次排障教会我一件事:眼见不一定为实——「网页能开」不代表「所有流量都通」,「看起来失败」也不一定是坏事。别被表象带着走,让 ssh -v 告诉你它到底在连谁。

希望这篇实录能帮你少踩一次坑。下次给协会 Gitea 配 SSH,记得先看一眼 ~/.ssh/config 里的 HostName 是不是直连了源站 😄