SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

Git SSH 地址详解:冒号后面的路径到底从哪里算起

git@my-vps:repo.git 冒号后的路径从哪里算起?GitHub 的 owner/repo 又是不是磁盘路径?用一个不联网的替身脚本看清 git 交给 ssh 的主机、端口和路径,再学会 22 端口不通时改走 443。

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, October 17, 2026.

一句话回答:Git SSH 地址冒号后面是谁的路径

Git SSH 地址冒号后面的路径怎么解释,取决于两件事:你用的是哪种写法,对面是谁。git@my-vps:repo.git 是 scp 风格的写法,路径会交给远程 shell,相对路径从 SSH 登录用户的家目录算起。ssh://git@my-vps:2222/srv/git/repo.git 是 URL 写法,主机名后面从斜杠开始就是路径,端口也只能写在这种地址里。git@github.com:owner/repo.git 里的 owner/repo 不是服务器磁盘上的路径。它是账号名加仓库名,由平台自己的程序去解读。

很多人第一次碰到这个问题,是在自己的 VPS 上建了一个裸仓库,照着 GitHub 的格式写了 git@my-vps:repo.git,结果 git 报告仓库不存在。或者仓库被建到了一个意想不到的目录里。原因几乎都在冒号后面这一段。下面按顺序讲清楚三种情况,再给一个完全不联网的方法,让你亲眼看到 git 到底把什么交给了 ssh。

如果你对 SSH 本身还不熟,可以先读 SSH 是什么、它怎样登录远程服务器。如果你还分不清 Git、GitHub 和自己的服务器各管什么,Git、GitHub 和 Git 服务器的区别 会先把概念理顺。

先认识 Git 的两种 SSH 地址写法

Git 官方文档(git clone 手册里的 GIT URLS 一节)为 SSH 协议列出了两种写法:

ssh://[<user>@]<host>[:<port>]/<path-to-git-repo>
[<user>@]<host>:/<path-to-git-repo>

第一种是标准的 URL 写法,下文叫它 ssh:// 写法。第二种文档称为“类似 scp 的语法”,因为它和 scp 命令的 主机:路径 格式一样,下文叫它 scp 风格。

文档对 scp 风格有一条限制:只有当第一个冒号之前没有斜杠时,git 才把它当作 SSH 地址。所以本地有一个目录真的叫 foo:bar 时,要写成 ./foo:bar,否则 git 会把 foo 当成主机名去连接。

请注意两种写法在语法上的区别。ssh:// 写法里有 [:<port>] 这个可选的端口位置。scp 风格的语法里没有端口位置。文档没有专门写一句“scp 风格不能带端口”,但它列出的语法里确实没有这一栏。这一点后面会反复用到。

scp 风格:git@my-vps:repo.git 指向服务器上的哪个目录?

要回答这个问题,需要把两份文档放在一起看。

第一份是 git 的源码。git 通过 SSH 拉取时,会在远程运行 git-upload-pack,推送时运行 git-receive-pack。它把冒号后面的路径用单引号包起来(源码 connect.c 里的 sq_quote_buf),拼在命令后面,作为一个字符串交给 ssh。git 自己并不解析这个路径。

第二份是 OpenSSH 的 sshd(8) 手册。它描述了用户登录后的步骤:先切换到该用户的家目录,然后用该用户在系统密码数据库里登记的登录 shell 来运行所有命令。

两者合起来,结论就清楚了:scp 风格里的相对路径,是从 SSH 登录用户的家目录开始算的。登录用户是 deploy,路径就从 deploy 的家目录算起。登录用户是 git,路径就从 git 的家目录算起。同一台服务器、同一个 repos/app.git,换一个登录用户,指向的就是另一个目录。

路径以斜杠开头,就是绝对路径,和家目录无关。git@my-vps:/srv/git/app.git 永远指向 /srv/git/app.git。

文档还为 SSH 协议列出了 ~用户名 的写法:git@my-vps:~alice/app.git 指向 alice 这个用户的家目录下的 app.git,不管你用哪个用户登录。

在自己的 VPS 上验证起点目录

先确认相对路径的起点。下面这条命令登录后只运行 pwd,打印出来的目录就是相对路径的起点:

ssh deploy@my-vps pwd

在常见的 Ubuntu 服务器上,它会打印 /home/deploy。然后在 VPS 上以 deploy 用户的身份建一个裸仓库:

mkdir -p ~/repos
git init --bare ~/repos/app.git

回到本地电脑,下面两条命令指向的是同一个仓库,前提是 deploy 的家目录确实是 /home/deploy:

git clone deploy@my-vps:repos/app.git
git clone deploy@my-vps:/home/deploy/repos/app.git

如果第一条报告仓库不存在,第二条却能成功,说明你的登录用户或者家目录和你以为的不一样。回头看 ssh deploy@my-vps pwd 的输出。如果两条都失败,而且报的是 Permission denied (publickey),那问题在登录这一步,还没有轮到路径,请看 如何排查 Permission denied (publickey) 报错。

scp 风格的地址为什么写不了端口?

因为 scp 风格的语法里没有端口这一栏,冒号后面的所有内容都属于路径。按照这个语法,git@my-vps:2222/repo.git 的意思并不是“连接 2222 端口”。它的意思是“连接默认端口,打开家目录下的 2222/repo.git”。

这是一个很常见的坑。你的 SSH 服务改到了非标准端口,你在冒号后面写上端口号,git 不会报语法错误。它会去默认端口连接,然后在一个奇怪的路径下找仓库。下一节的替身脚本可以让你亲眼看到这个结果。

需要端口时,有两个办法。一是改用 ssh:// 写法。二是在 ~/.ssh/config 里给这台主机写一个 Port。第二种办法能用,是因为 git 只把主机名交给 ssh 程序,ssh 会再去读自己的配置文件。后面讲 GitHub 的 443 端口时会用到它。

ssh:// 写法:路径从根目录开始,端口写在主机后面

ssh:// 写法看起来像网址,规则也像网址。主机后面的冒号接端口,第一个斜杠开始是路径:

ssh://git@my-vps:2222/srv/git/app.git

这个地址的意思是:用 git 用户,连接 my-vps 的 2222 端口,打开 /srv/git/app.git。这里的路径按绝对路径理解,所以这种写法最适合仓库放在固定位置(例如 /srv/git)的服务器。

想在 ssh:// 写法里表示某个用户的家目录,文档给的写法是 /~用户名/:

ssh://git@my-vps/~alice/app.git

有些教程还会写 ssh://git@my-vps/~/app.git,即只有 ~ 没有用户名,表示登录用户自己的家目录。git 文档里只列出了带用户名的 /~用户名/,没有列出单独的 /~/。如果你想用它,先用下一节的方法在自己的 git 版本上看一眼结果,不要直接假设它可用。

不联网,亲眼看 git 把什么交给了 ssh

前面讲的都来自文档和源码。最可靠的做法是自己看。git 允许用环境变量 GIT_SSH_COMMAND 替换掉 ssh 程序。按 git 手册的说法,它的优先级高于 GIT_SSH,而且会经过 shell 解释,所以可以带额外参数。我们用一个替身脚本代替 ssh:它不连接任何地方,只把 git 给它的每个参数打印出来,然后退出。

mkdir -p ~/git-ssh-demo
cat > ~/git-ssh-demo/fake-ssh <<'EOF'
#!/bin/sh
for a in "$@"; do printf '[%s] ' "$a" >&2; done
printf '\n' >&2
exit 1
EOF
chmod +x ~/git-ssh-demo/fake-ssh

每个参数都用方括号包起来,这样参数里的空格和引号都看得清。

还有一个细节。git 要判断你的 ssh 程序是 OpenSSH 还是 PuTTY 的 plink 之类,因为它们传端口的参数不同:OpenSSH 用 -p,plink 和 putty 用 -P。程序名不认识时,git 会先用 -G 参数试探一次。我们的脚本名叫 fake-ssh,git 不认识,所以要用 -c ssh.variant=ssh 直接告诉它“这就是 OpenSSH”,省掉试探。设置环境变量 GIT_SSH_VARIANT=ssh 效果相同,它会覆盖 ssh.variant 配置。

现在把各种写法逐个交给 git:

for url in \
  'git@my-vps:repo.git' \
  'git@my-vps:/srv/git/repo.git' \
  'git@my-vps:~alice/repo.git' \
  'git@my-vps:2222/repo.git' \
  'ssh://git@my-vps/srv/git/repo.git' \
  'ssh://git@my-vps:2222/srv/git/repo.git' \
  'ssh://git@my-vps/~alice/repo.git' \
  'ssh://git@my-vps/~/repo.git' \
  'git@github.com:owner/repo.git' \
  'ssh://git@ssh.github.com:443/owner/repo.git'
do
  echo "== $url"
  GIT_SSH_COMMAND="$HOME/git-ssh-demo/fake-ssh" \
    git -c ssh.variant=ssh ls-remote "$url" 2>&1 | head -n 1
done
git --version

替身脚本以失败状态退出,所以 git 随后会报错,这是预期的。head -n 1 只保留第一行,也就是替身脚本打印的参数。整个过程不做 DNS 查询,也不连接任何主机,因为真正的 ssh 根本没有运行。

读输出时看三处:

  • 主机参数:方括号里的 git@my-vps 或 git@ssh.github.com,这是 ssh 要连接的用户和主机。
  • 端口:有没有出现 [-p] 后面跟一个端口号。只有地址里真的写了端口,才应该出现它。
  • 最后一个参数:git-upload-pack 后面跟着用单引号包住的路径。这就是 sshd 交给登录 shell 运行的那一行。

重点对比几组。git@my-vps:repo.git 的路径不以斜杠开头,所以登录 shell 会从家目录开始找。git@my-vps:2222/repo.git 有没有出现 -p?2222 跑到了哪里?ssh://git@my-vps:2222/... 和它相比又有什么不同?~alice 和单独的 ~ 在两种写法里分别变成了什么?

最后一行 git --version 请记下来。上面说的参数格式来自当前的 git 文档和源码。参数顺序和附加选项可能随版本变化,所以请以你机器上实际打印的结果为准。

如果想换一种方法,git 手册说明 GIT_TRACE=1 会把跟踪信息打印到 stderr,其中包括外部命令的执行。你可以对一个不存在的主机试试 GIT_TRACE=1 git ls-remote git@example.invalid:repo.git。这一次运行的是真正的 ssh,它会尝试解析主机名然后失败。跟踪输出里能看到多少 ssh 参数,取决于 git 版本。

GitHub、GitLab、Gitea:owner/repo 不是磁盘路径

到了代码托管平台,冒号后面的路径换了一种意思。

先看 GitHub。GitHub 文档给出的 SSH 远程地址都是 git@github.com:OWNER/REPOSITORY.git 这种形式,每个人都用 git 这个用户名登录。可是用 ssh -T git@github.com 测试连接成功时,GitHub 会叫出你自己的账号名:

Hi USERNAME! You've successfully authenticated, but GitHub does not provide shell access.

大家用同一个用户名登录,GitHub 却知道你是谁,能区分你的只有你的密钥。后半句也说明了:GitHub 不提供 shell。既然没有 shell,就不存在“从家目录算起”这回事。

GitLab 的文档写得更直接:默认情况下连接使用 git 用户名,一把 SSH 密钥就能让你访问账号有权限的所有项目和群组。GitLab 的管理员文档还讲到,在启用快速密钥查找的部署方式下,GitLab Shell 用 SSH 密钥的指纹来判断用户是否有权访问。

还要注意,GitLab 地址里冒号后面的部分不一定是“账号名/仓库名”。GitLab 按命名空间来定位项目。它的文档说明,个人命名空间基于用户名,群组或子群组的命名空间基于群组或子群组的名称,并以 alex-team/marketing 作为子群组命名空间的例子。所以 GitLab 上的项目路径可能是 群组/子群组/项目 这样的多级结构,例如 git@gitlab.example.com:alex-team/marketing/website.git。

Gitea 的命令行文档介绍了 gitea keys 命令,它作为 sshd 的 AuthorizedKeysCommand 使用,示例里用的也是共用的 git 账号:

AuthorizedKeysCommand /path/to/gitea keys -e git -u %u -t %t -k %k

Forgejo 的命令行文档里有一个 serv 子命令,说明是“内部命令,只应由 SSH shell 调用”。

这些平台背后通常依靠 OpenSSH 的“强制命令”机制。sshd(8) 手册说明:在 authorized_keys 里给某把密钥加上 command= 选项,这把密钥每次登录时都会运行那个固定的命令,而客户端原本请求的命令放在环境变量 SSH_ORIGINAL_COMMAND 里。服务端的 ForceCommand 指令也能做到同样的事。于是 git 发过去的 git-upload-pack 'owner/repo.git' 不会被 shell 当作路径执行。它只是一个字符串,由平台的程序读出来,再按其中的命名空间和仓库名去查自己的数据库。在 GitHub 上这是账号名加仓库名,在 GitLab 上可能是多级的群组路径加项目名。各平台的具体实现细节并不完全相同,上面是这类系统常见的做法。

所以在托管平台上,不用去想家目录和绝对路径的问题。地址一律复制平台页面给出的那一行。需要你操心的是端口,以及登录用的是哪一把密钥,后者可以参考 SSH 密钥管理基础。

自建平台常常把 SSH 放在非标准端口上。例如 Forgejo 的 Docker 安装文档用 ssh -F /dev/null git@<address> -p 222 测试 SSH。端口不是 22 时,git 地址要么写成 ssh://git@你的域名:222/owner/repo.git,要么在 ~/.ssh/config 里写好 Port。如果你还在选择自建方案,可以看 自建 Git 服务器的几种选择。

22 端口连不上 GitHub 怎么办:改用 ssh.github.com 的 443 端口

在公司网络、校园网,或者跨境线路不稳定的网络里,连接 github.com 的 22 端口经常超时或被重置。GitHub 官方文档给出的办法是:改为连接 ssh.github.com 的 443 端口。443 是 HTTPS 用的端口,网络放行它的可能性大得多。

先测试这条路是否通:

ssh -T -p 443 git@ssh.github.com

切换到 443 端口后的第一次连接,ssh 可能会显示主机密钥警告。GitHub 文档说明,只有当指纹和 GitHub 公布的指纹之一完全一致时,才可以回答 yes。请打开 GitHub 文档里公布 SSH 密钥指纹的页面,逐字对比,不要凭印象。

测试通过后,有两种用法。

第一种,用 ssh:// 写法,把主机和端口直接写进地址。这是 GitHub 文档里给的克隆示例:

git clone ssh://git@ssh.github.com:443/YOUR-USERNAME/YOUR-REPOSITORY.git

scp 风格表达不了这件事,因为它的语法里没有端口一栏。写成 git@ssh.github.com:443/...,443 只会变成路径的一部分。

第二种,在 ~/.ssh/config 里加一段配置,这也是 GitHub 文档给出的写法:

Host github.com
    Hostname ssh.github.com
    Port 443
    User git

OpenSSH 的 ssh_config(5) 手册解释了每一行:Host 让下面的设置只对匹配的主机生效,HostName 指定真正要登录的主机,Port 指定端口(默认是 22),User 指定登录用户。有了这一段,现有的 git@github.com:owner/repo.git 一个字都不用改。git 把主机名 github.com 交给 ssh,ssh 读到这段配置,自己改去连接 ssh.github.com 的 443 端口。scp 风格本身仍然没有端口,端口是 ssh 从配置文件里补上的。

两种方法怎么选?只改一个仓库,用 ssh:// 写法。整台电脑上所有 GitHub 仓库都要走 443,用配置文件。如果连 443 也超时,问题就不在端口选择上了,SSH 连接被拒绝和连接超时的区别 能帮你判断是哪一层出了问题。

用 git remote -v 和 git remote set-url 切换写法

已经克隆好的仓库,不需要重新克隆。先看当前的远程地址:

git remote -v

按照 git 手册,-v 会在远程名称后面显示地址,拉取(fetch)和推送(push)各一行:

origin  git@github.com:OWNER/REPOSITORY.git (fetch)
origin  git@github.com:OWNER/REPOSITORY.git (push)

把它改成走 443 端口的 ssh:// 写法:

git remote set-url origin ssh://git@ssh.github.com:443/OWNER/REPOSITORY.git
git remote -v

再运行一次 git remote -v,两行都应该变成新地址。如果只想改推送地址,加上 --push:

git remote set-url --push origin ssh://git@ssh.github.com:443/OWNER/REPOSITORY.git

自己的 VPS 改了 SSH 端口时,做法相同:

git remote set-url origin ssh://deploy@my-vps:2222/home/deploy/repos/app.git

注意这里的 /home/deploy/repos/app.git。原来的 scp 风格地址如果写的是相对路径 repos/app.git,改成 ssh:// 写法时要补成完整的绝对路径,因为 ssh:// 写法里斜杠后面按绝对路径理解。

同一个仓库,两种写法是同一个远程吗?

对 git 来说,远程地址就是你写下的那串文字。我们没有在 git 文档里找到“把两种写法识别为同一个仓库”的说明。你可以在一个测试仓库里自己验证:

git remote add a git@my-vps:repos/app.git
git remote add b ssh://git@my-vps/home/deploy/repos/app.git
git remote -v

如果 a 和 b 都被列出来,说明 git 接受了两个远程,尽管它们可能指向同一个仓库。这会影响依赖地址匹配的功能。git 的 url.<base>.insteadOf 配置就是一个例子。手册说明它会把以某个值开头的地址,改写成以 <base> 开头。它比较的是开头的文字:

git config --global url."ssh://git@ssh.github.com:443/".insteadOf "git@github.com:"

这条配置会把所有 git@github.com: 开头的地址改走 443 端口。但它不会改写 ssh://git@github.com/ 开头的地址,因为那串文字的开头不一样。同时向多个地址推送时也要注意这一点,可以参考 同时推送到 GitHub 和自己的 Git 服务器。

FAQ

git@github.com:owner/repo.git 里的 owner/repo 是服务器上的路径吗?

不是。GitHub 不给 git 用户提供 shell,所以这段文字不会被当作文件路径。git 把它放进 git-upload-pack 或 git-receive-pack 命令里发给服务器,平台的程序读出账号名和仓库名,再根据你的 SSH 密钥判断你有没有权限。GitLab、Gitea、Forgejo 等托管平台也是由自己的程序来解读这一段,其中 GitLab 的这一段是项目所在的命名空间加项目名,可能包含多级群组和子群组。复制平台页面给出的地址即可。

git@my-vps:repo.git 指向服务器上的哪个目录?

指向 SSH 登录用户的家目录下的 repo.git。git 把路径原样交给远程,sshd 登录后先切换到该用户的家目录,再用登录 shell 运行命令,所以相对路径从家目录算起。运行 ssh 用户@my-vps pwd 可以看到这个起点。路径以斜杠开头时是绝对路径,写成 ~alice/repo.git 时指向 alice 的家目录。

scp 风格的 Git 地址怎么指定 SSH 端口?

不能写在地址里。git 文档列出的 scp 风格语法没有端口一栏,冒号后面全部算作路径,所以 git@my-vps:2222/repo.git 会在默认端口上找 2222/repo.git。改用 ssh://git@my-vps:2222/绝对路径 写法,或者在 ~/.ssh/config 里为这台主机写上 Port 2222,原来的 scp 风格地址就能继续使用。

连接 GitHub 的 22 端口总是超时,怎么办?

按 GitHub 官方文档改用 ssh.github.com 的 443 端口。先运行 ssh -T -p 443 git@ssh.github.com 测试,第一次连接时把主机密钥指纹和 GitHub 公布的指纹逐字对比。然后在 ~/.ssh/config 里加上 Host github.com、Hostname ssh.github.com、Port 443、User git 四行,或者用 git remote set-url origin ssh://git@ssh.github.com:443/OWNER/REPOSITORY.git 修改单个仓库的地址。

#git#SSH#github#git-server#remote-url