SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-10-20

Git、GitHub 与自托管 Git 服务器有什么区别

Git 可离线运行,GitHub 是一种远程仓库托管服务,VPS 可保存第二份副本。本文解释三者区别,并介绍使用 SSH 将仓库连接到自托管远程端的步骤。

Git、GitHub 与 Git 服务器:简要说明

Git 是安装在计算机上的程序。它无需账户或网络即可运行。GitHub 是托管服务,用于保存 Git 仓库的副本,并在其上提供网站界面。Git 服务器是开发者控制的任意计算机,用于保存另一个副本,并通过 SSH(安全 Shell)访问。三者都包含“git”一词,因此一年级学生会在同一节实验课中接触到它们,最后误以为它们是同一个东西。

这种差异一段时间内看起来只是理论上的。以下 4 个时刻会让差异变得实际:客户要求代码存放在其控制的基础设施上时,持续集成(CI)配额用尽时,GitHub 无法访问但仍必须创建提交时,以及某个账户保存着 4 年课程作业的唯一副本时。本指南将区分这三者,然后介绍如何将一个 GitHub 账户连接到虚拟专用服务器(VPS)上的第二个远程仓库,并使用 SSH 密钥而不是密码。

网络关闭时,Git 在笔记本电脑上的工作方式

git init 会在项目目录顶层创建一个名为 .git 的隐藏目录。每个提交、分支、标签以及完整历史记录都保存在该磁盘上的这个目录中。不会有任何内容发送到其他位置。

mkdir demo && cd demo
git init
git config user.name "Example Student"
git config user.email "student@example.com"
echo "hello" > README.md
git add README.md
git commit -m "First commit"
git log --oneline

git log --oneline 会输出一行,其中包含简短的提交哈希和提交消息。即使关闭 Wi-Fi、在拔掉网线的实验室中,或在火车上,这条命令也能正常工作。该代码块中的任何步骤都不会打开网络套接字,因此任何步骤都不会因网络问题而失败。

“远程仓库”是指向另一个同一仓库副本所在 URL 的已保存名称。新建的仓库没有远程仓库,git remote -v 不会输出任何内容。只有在有人添加远程仓库并运行 git push 后,GitHub 才会参与其中。了解这一点可以消除大多数困惑。Git 不依赖 GitHub 也能完整工作,而 GitHub 只是众多可用于复制 Git 仓库的位置之一。

分布式意味着每个克隆都是完整的仓库。上周克隆了项目的笔记本电脑仍然保存着该项目的完整历史记录,包括克隆发生之前创建的每个提交。它不是旧式版本控制系统中的那种中央副本的精简检出。

GitHub 在 Git 之上增加的功能

拉取请求、问题跟踪、评审讨论串、Actions 工作流、发布版本、复刻、组织权限和依赖项警报。这些都不是 Git 的功能。Git 没有拉取请求的概念。它提供的最接近功能是 git request-pull,该命令会输出纯文本摘要,供用户粘贴到电子邮件中。评审界面、合并按钮和评论讨论串都是 GitHub 的产品功能,构建在 Git 之上,而不是 Git 内部的一部分。GitHub 作为托管服务的介绍对此有详细说明。

存储部分使用的是普通 Git。对 GitHub URL 执行 git clone 后,会生成一个仓库;之后提交、分支、合并或查看历史记录都不再需要 GitHub。克隆操作不会带走围绕代码展开的讨论,因为这些内容从一开始就不存储在 Git 中。

截至 2026 年 10 月,GitHub 学生账户包含的内容

GitHub Free 包含私有仓库,因此课程作业无需公开即可存储。公开仓库可以免费使用 GitHub 托管的 CI runner,私有仓库则使用每月包含的额度。

Actions 分钟数和软件包存储空间的具体额度会变化,因此教程中写下的任何数字几个月后都可能失效。GitHub 在 https://github.com/pricing 提供的定价页面才是值得信任的数据来源,本指南特意不重复这些数字。

在验证学生身份后,https://education.github.com/pack 提供的 GitHub Student Developer Pack 还会增加更多权益。GitHub 要求提供证明当前在读状态的文件,例如显示当前注册日期的学校证件照片、课程表、成绩单或在读证明信。如果同一学校近期申请者使用学术邮箱地址完成了验证,GitHub 也会要求使用该地址;申请前必须先将其添加到账户并完成验证。如果申请所用的学校邮箱域名无法被识别,应联系 GitHub Education Support。礼包中的权益由其他公司提供,列表也经常变化,因此礼包页面是唯一可靠的权益清单。

有一点警告比任何额度都重要。学生账户是单个账户。多因素身份验证(MFA)设备丢失、账户被暂停或误将账户转移到组织,都会立即导致无法访问所有私有仓库。启用双因素身份验证时,GitHub 会生成恢复代码。将这些代码保存在笔记本电脑以外的位置,决定了问题是暂时延迟,还是永久无法登录。另一个 remote 是这份保障的另一部分。

为什么这种差异不再只是理论问题

客户端要求代码保留在其控制的基础设施上。 某些合同和大多数公共部门招标文件会规定源代码和构建产物可以存放的位置。托管账户无法满足明确指定服务器和国家的条款。位于该国家的 VPS 上的裸仓库可以满足要求,而开发者仍可按原有方式工作。

CI 配额用完。 对于私有仓库,后续行为取决于账户的计费设置。如果账户未登记付款方式,GitHub 会在包含的配额用完后阻止使用 Actions,直到下一个计费周期开始时配额重置。如果账户已登记付款方式,工作流会继续运行,额外分钟数将计入该付款方式,除非设置为停止使用的预算限制了支出。无论哪种情况,仓库本身都不会受到影响。一种解决方法是继续使用 GitHub 作为远程仓库,但将计算任务迁移到成本更低的位置:在 VPS 上运行自托管 GitHub Actions runner,使用相同的工作流文件,在按月计费而不是按分钟计费的机器上执行。根据 GitHub 的计费文档,截至 2026 年 10 月,自托管 runner 的使用是免费的。

GitHub 无法访问。 发生服务中断时,或者校园网络阻止出站连接时,git commit 仍可正常工作,因为提交是本地操作。只有 git push 和 git fetch 会失败。工作可以继续,提交会在本地排队,直到网络恢复。在 VPS 上配置第二个远程仓库后,这些排队的提交仍可推送成功。

一个账户保存唯一副本。 两个远程仓库意味着两个相互独立的副本,也意味着两种相互独立的故障模式。日常工作流无需改变,只需额外执行一次推送。

如何在 VPS 上添加第二个远程仓库

最小的可用 Git 服务器由 OpenSSH 和 git 软件包组成。它不提供 Web 界面,也不使用数据库。Git 通过 SSH 连接使用自己的协议,因此能够通过 SSH 登录的账户已经可以托管代码仓库。

在运行 Ubuntu 24.04 的 VPS 上,以具有 sudo 权限的用户身份执行:

sudo apt update && sudo apt install -y git
sudo adduser --disabled-password --gecos "" git
sudo install -d -m 700 -o git -g git /home/git/.ssh
sudo install -d -m 755 -o git -g git /home/git/repos

--disabled-password 创建的账户没有可用密码,因此只能通过密钥登录。接下来是开发者的公钥。.pub 文件是公钥部分,可以安全地复制到其他位置。不带 .pub 的文件绝不能离开笔记本电脑。

sudo tee -a /home/git/.ssh/authorized_keys < /tmp/id_ed25519.pub > /dev/null
sudo chown git:git /home/git/.ssh/authorized_keys
sudo chmod 600 /home/git/.ssh/authorized_keys

这些模式是必需的。sshd 默认启用 StrictModes,因此如果 /home/git 或 /home/git/.ssh 对组用户或其他用户可写,它会忽略密钥文件。此时客户端只会看到 Permission denied (publickey).,完全没有任何解释,而服务器日志会记录真实原因:Authentication refused: bad ownership or modes for directory /home/git/.ssh。

创建裸仓库:

sudo -u git git init --bare /home/git/repos/project.git

裸仓库不包含工作树。该目录只包含 .git 文件夹通常包含的内容,不包含其他内容。这一点很重要,因为 Git 拒绝推送到普通仓库当前检出的分支,并显示 remote: error: refusing to update checked out branch: refs/heads/main。裸仓库没有检出的分支,因此可以接受所有推送。

回到笔记本电脑,在现有项目中:

git remote -v
git remote add vps git@203.0.113.10:repos/project.git
git push vps --all
git push vps --tags
git ls-remote vps

更改前,git remote -v 会将 GitHub 远程仓库打印两次:一次标记为 (fetch),另一次标记为 (push)。更改后,它会打印四行。git ls-remote vps 会为服务器上的每个 ref 打印一行及其完整哈希值,这证明对象已上传到服务器。输出为空表示没有推送任何内容。

路径 repos/project.git 是相对于 git 用户的主目录的,因此会解析为 /home/git/repos/project.git。也可以使用绝对路径。请注意,git push vps --all 会推送所有分支,但不会推送标签,因此推送标签需要单独执行命令。

如果 VPS 监听的端口不是 22,简写形式 git@host:path 无法包含该端口。完整 URL 形式可以:ssh://git@203.0.113.10:2222/home/git/repos/project.git。修改 SSH 端口是常见的加固措施;在 SELinux 和 firewalld 系统上修改 SSH 端口介绍了 Rocky 和 AlmaLinux 中容易遗漏的配置步骤。

为什么 SSH 密钥会取代两个远程端的密码

这里两个远程端都使用 SSH(安全 Shell)协议,SSH 如何验证连接介绍了底层的握手过程。实际过程很简单:生成一次密钥对,将公钥交给每台服务器,私钥即可证明身份,且私钥始终不会被传输。

ssh-keygen -t ed25519 -C "student@example.com"
cat ~/.ssh/id_ed25519.pub

GitHub 端使用同一个公钥。将公钥粘贴到帐户设置中的 SSH keys 页面即可。测试只需执行一条命令:

ssh -T git@github.com

密钥有效时会输出 Hi username! You've successfully authenticated, but GitHub does not provide shell access.。这表示成功,不是错误。GitHub 特意不为该帐户提供 Shell。

从 13 August 2021 起,通过 HTTPS 使用密码执行 Git 操作已停止工作,提示信息会明确说明这一点:remote: Support for password authentication was removed on August 13, 2021. HTTPS 现在需要个人访问令牌。个人访问令牌本质上是带有作用域列表和过期日期的密码。密钥无需经历这种定期更新流程。使用多台计算机和多台服务器时,密钥管理本身会变成一项记录维护工作,跨计算机管理 SSH 密钥介绍了如何处理这一问题。

许多校园和办公网络会阻止出站端口 22。其表现为 ssh: connect to host github.com port 22: Connection timed out。GitHub 也在端口 443 上提供 SSH,因此在 ~/.ssh/config 中添加以下 4 行即可解决:

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

ssh -T git@github.com 随后即可通过 443 成功连接。当密钥看起来正确但连接仍被拒绝时,Permission denied (publickey) 背后的具体原因列出了客户端看来相同的各种原因。

SSH 对互联网开放的 VPS 在首次启动后的几小时内就会收到登录尝试。最重要的是设置以下两项:文件 /etc/ssh/sshd_config.d/ 下的 PasswordAuthentication no,以及在 Ubuntu 24.04 上为 SSH 配置 fail2ban,以丢弃持续尝试连接的主机。

如何用一条命令推送到 GitHub 和 VPS

两个远程仓库使用两个名称,就需要执行两次推送。Git 也可以在一个远程仓库名称下配置多个 URL,并通过一次推送将内容发送到多个 URL:

git remote set-url --add --push origin git@github.com:student/project.git
git remote set-url --add --push origin git@203.0.113.10:repos/project.git
git remote -v

现在,git remote -v 会为 origin 显示两行 (push)。git push origin main 会按照列出的顺序向两个 URL 写入。必须显式将 GitHub URL 添加为第一个 URL,因为添加任何推送 URL 都会替换从获取 URL 继承的隐式默认 URL。获取操作仍只使用获取 URL,因此这属于写入扇出,而不是双向同步。

如果第二个副本仅用于备份,镜像是更严格的选项:

git clone --mirror git@github.com:student/project.git project.git
cd project.git
git remote set-url --push origin git@203.0.113.10:repos/project.git
git fetch -p origin
git push --mirror

该命令会复制所有 ref,包括没有人检出的分支和标签。--mirror 还会删除目标端存在、但源端已不存在的 ref。对于备份来说这是正确行为;如果操作方向意外反转,则会造成破坏性后果。

出现的问题及每次打印的消息

目录错误。 fatal: not a git repository (or any of the parent directories): .git表示命令在任何仓库之外运行。没有任何内容损坏,也与网络无关。

名称解析失败。 fatal: unable to access 'https://github.com/student/project.git/': Could not resolve host: github.com是 DNS(域名系统)错误。提交仍可正常进行;只有传输失败。

密钥未被接受。 git@203.0.113.10: Permission denied (publickey).表示服务器拒绝了客户端提供的所有密钥。ssh -v git@203.0.113.10列出尝试过的密钥文件。在服务器上,sudo journalctl -u ssh -n 50说明了实际原因,最常见的是前文所述的目录权限。

登录成功,但路径不存在。 fatal: 'repos/project.git' does not appear to be a git repository后面跟着 fatal: Could not read from remote repository.,表示 git init --bare未运行,或相对于 git用户主目录的路径不正确。

远程仓库领先。 ! [rejected] main -> main (fetch first)表示服务器包含本地克隆中没有的提交。推送前必须先执行 git fetch,然后进行合并或变基。

目标不是裸仓库。 remote: error: refusing to update checked out branch: refs/heads/main表示服务器仓库使用 git init而不是 git init --bare创建。

自托管 Git 服务器不提供什么

通过 SSH 访问的裸仓库只提供基于密钥的存储和访问控制。它不提供 issue 跟踪器、拉取请求界面、代码 Web 视图或 CI。这些功能需要在其上安装软件才能提供。Gitea 和 Forgejo 体积较小,可运行在一个容器中。GitLab 的规模大得多,需要更多内存。cgit 和 Gitweb 只提供只读视图,不提供其他功能。自托管 Git 服务器选项及其运行成本对它们进行了准确比较。

自行负责也意味着要负责备份,而这部分确实很简单,因为裸仓库就是一个普通目录:

sudo tar czf /root/project-$(date +%F).tar.gz -C /home/git/repos project.git

将该文件复制到服务器之外的位置,即可完整恢复仓库。不会自动提供的是高可用性、磁盘空间和安全更新。其背后没有支持团队。对于完全不应接受任何入站连接的服务器,无需开放任何入站端口发布服务是另一种方案;它不是消除信任,而是将信任转移给隧道提供商。

对大多数人来说,诚实的答案是两者都用。GitHub 保留协作工具,以及用于公共项目的免费 CI。VPS 保留一份副本,不会因账号暂停或服务中断而丢失。希望完全自行掌控一切(包括评审界面)的读者,应从自托管 Git 服务器选项比较开始,然后选择与现有硬件相匹配的方案。

FAQ

Git 和 GitHub 是同一个东西吗?

不是。Git 是安装在计算机上的版本控制程序,无需账户或网络连接即可运行。GitHub 是一家公司的托管服务,用于存储 Git 仓库的副本,并围绕仓库提供拉取请求、议题和 Actions。即使 GitHub 消失,从 GitHub 克隆的仓库仍会保留所有提交和分支,因为每个克隆都包含完整历史记录。

学生可以免费使用 GitHub 吗?

可以。GitHub Free 包含私有仓库,GitHub Student Developer Pack 位于 https://education.github.com/pack,学生使用证明当前在读状态的文件(例如显示当前在读日期的学生证)完成验证后,还可获得更多优惠;如果学校要求,还需提供学术邮箱。包含的 Actions 分钟数和存储空间会随时间变化,因此应以 https://github.com/pricing 中的数值为准,而不要使用指南中引用的任何数字。本页面编写于 2026 年 8 月,因此特意省略这些数值。

git commit 无需互联网连接也能运行吗?

可以。提交会将对象写入本地 .git 目录,并移动一个分支指针。任何内容都不会离开本机。只有 git push、git fetch、git pull 和 git clone 需要网络,因此网络中断会延迟共享工作,而不会阻止完成工作。

为什么服务器上的仓库必须是裸仓库?

因为普通仓库会检出一个分支,而 Git 不允许修改可能正被他人编辑的工作树中的文件。推送会因 remote: error: refusing to update checked out branch: refs/heads/main 被拒绝。git init --bare 会创建一个没有工作树的仓库,因此可以接受推送到任何分支。

VPS 远程仓库应该命名为 origin 吗?

远程仓库名称只在各个克隆中本地有效,因此这取决于习惯,而不是规则。在协作者视为权威仓库的远程仓库上保留 origin,可以避免共享说明产生歧义;将第二个远程仓库命名为 vps 或 backup,可以在 git remote -v 中明确表达其用途。