内网自建Git服务实战:CentOS 8 + Apache HTTP协议搭建指南
发布时间:2026/10/12 12:54:14 锦皓数字建站

1. 为什么要在内网虚拟化环境里自建 Git 服务很多团队一开始图省事直接把代码托管在第三方平台上。但真到了某些场景——比如内网开发、涉密项目、实验室离线环境或者单纯想省一笔团队协作的订阅费——自建 Git 服务就成了刚需。我这次要聊的就是在一台跑着 VMware 的宿主机上装一个 CentOS 8 虚拟机然后基于 HTTP 协议把 Git 服务搭起来。先说清楚这套方案解决什么问题。它让你拥有一个完全自主可控的代码仓库团队成员通过浏览器能看提交记录通过命令行能 clone、push、pull权限自己管数据在自己硬盘上。适合谁适合中小团队的内网协作、个人多设备同步代码、教学演示环境以及任何不想把代码放到公网上的场景。为什么选 HTTP 协议而不是 SSH这是很多人纠结的第一个点。SSH 方式配置密钥对确实方便但 HTTP 有几个绕不开的优势第一穿透性好内网防火墙通常放行 80 或 8080 端口而 22 端口经常被限制第二对客户端零配置要求新同事入职只要给个账号密码就能拉代码不用生成密钥、上传公钥那一套流程第三方便做统一的认证和审计后面想接 LDAP 或者做访问日志都更顺手。当然 HTTP 也有代价配置比 SSH 略繁琐传输大仓库时性能稍逊但对绝大多数中小规模项目来说完全够用。至于为什么用 CentOS 8 而不是 7 或者别的发行版主要是 CentOS 8 自带的软件包版本比较新git和httpd的版本都能满足现代 Git 服务的要求而且systemd管理服务的方式统一后续维护省心。VMware 则是大多数人在 Windows 或 Mac 上跑 Linux 虚拟机的首选快照功能让实验环境可以随便折腾搞坏了回滚就行。提示CentOS 8 已经停止官方维护如果你是在生产环境部署建议考虑迁移到兼容的替代发行版。本文以 CentOS 8 作为演示环境操作逻辑在其他同类系统上基本通用。整篇文章我会按照真实搭建的顺序来讲先准备虚拟机环境再装依赖、配 Apache、建仓库、设权限最后跑通完整的 clone 和 push 流程。中间会穿插我踩过的坑和验证过的参数尽量让你一次成功。2. 虚拟机与系统环境的准备细节2.1 VMware 里创建 CentOS 8 虚拟机的关键参数打开 VMware Workstation新建虚拟机选择自定义而不是典型因为自定义能让你控制磁盘和网络这些关键项。客户机操作系统选 Linux版本选 CentOS 8 64 位。处理器给 2 核、内存给 2GB 就够跑 Git 服务了但如果你打算同时在这台机器上跑 CI 或者别的服务建议内存拉到 4GB。磁盘这块有个经验不要用默认的 20GB 拆分磁盘改成单文件、给 40GB。Git 仓库随着提交历史增长会越来越占空间20GB 用不了多久就紧张。网络适配器选桥接模式还是NAT 模式取决于你的需求——桥接模式下虚拟机会拿到和宿主机同网段的独立 IP局域网内其他机器能直接访问NAT 模式下虚拟机通过宿主机上网外部访问需要做端口转发。内网团队协作场景我推荐桥接省去端口映射的麻烦。安装 CentOS 8 的时候软件选择界面勾选最小安装就行别装图形界面浪费资源。但要注意最小安装默认不带wget和vim装完系统后第一件事就是补上这两个工具。网络配置在安装界面就可以设好把主机名改成有意义的名字比如git-server后面配置 Apache 的时候会用到。2.2 装完系统后的初始化操作系统装好第一次登录先用ip addr确认网络通了然后执行更新。CentOS 8 的默认仓库地址已经失效需要先替换成可用的镜像源否则dnf update会直接报错。替换方法很简单把/etc/yum.repos.d/下的 repo 文件里的mirrorlist行注释掉把baseurl指向可用的镜像地址即可。更新完成后关闭防火墙或者放行 HTTP 端口。我个人的习惯是内网环境直接关掉 firewalld减少排查问题的干扰systemctl stop firewalld systemctl disable firewalld如果你坚持要开防火墙那就放行 80 和 8080 端口firewall-cmd --permanent --add-port80/tcp firewall-cmd --permanent --add-port8080/tcp firewall-cmd --reload还有 SELinux这个东西在配置 Git 服务时会带来不少麻烦尤其是仓库目录的权限问题。我建议先把它设为宽容模式等全部配置跑通后再考虑是否开启setenforce 0 sed -i s/SELINUXenforcing/SELINUXpermissive/g /etc/selinux/config注意生产环境不建议直接关闭 SELinux正确做法是为 Git 仓库目录设置合适的 SELinux 上下文。但作为搭建初期的排错手段先设为 permissive 能帮你排除掉一大类权限报错。2.3 安装 Git 和 Apache 的正确姿势CentOS 8 的 AppStream 仓库里自带git和httpd直接一条命令搞定dnf install -y git httpd装完之后验证版本git --version httpd -v我实测下来CentOS 8 默认源里的 Git 版本是 2.27 左右Apache 是 2.4这两个版本配合起来做 HTTP 协议的 Git 服务完全没问题。不需要去编译源码装最新版除非你有特殊需求。这里有个细节值得说git包安装后git-http-backend这个 CGI 程序位于/usr/libexec/git-core/目录下。这个路径很关键后面配置 Apache 的时候要指向它。你可以先用ls确认一下这个文件存在ls -l /usr/libexec/git-core/git-http-backend如果这个文件不在说明你的 Git 安装不完整需要重新安装或者检查包管理器源。3. Apache 与 git-http-backend 的对接配置3.1 理解 git-http-backend 的工作机制在动手改配置文件之前先花两分钟搞明白这套东西是怎么跑起来的后面出问题你才知道往哪查。Git 通过 HTTP 协议通信走的是哑协议和智能协议两套机制。哑协议就是单纯把仓库目录当静态文件服务器客户端直接下载 objects 文件效率低且不支持 push。智能协议则是通过git-http-backend这个 CGI 程序动态处理请求它负责解析客户端的git-upload-pack拉取和git-receive-pack推送请求然后调用对应的 Git 命令完成数据传输。Apache 在这里扮演的角色是 Web 服务器加认证网关。当客户端访问http://服务器地址/git/仓库名.git/info/refs?servicegit-upload-pack这样的 URL 时Apache 根据配置把请求转发给git-http-backend处理同时负责验证用户名密码。所以整个链路是客户端 → Apache认证→ git-http-backendCGI→ Git 命令 → 仓库数据。理解了这条链路你就知道配置的重点在哪一是让 Apache 知道哪些 URL 要交给 CGI 处理二是设置好认证方式三是保证 Apache 运行用户对仓库目录有读写权限。3.2 编写 Apache 虚拟主机配置我习惯把 Git 服务的配置单独放在一个文件里而不是直接改主配置文件这样管理起来清晰。创建/etc/httpd/conf.d/git.confSetEnv GIT_PROJECT_ROOT /var/git/repositories SetEnv GIT_HTTP_EXPORT_ALL SetEnv REMOTE_USER$REDIRECT_REMOTE_USER ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ Directory /usr/libexec/git-core Options ExecCGI Require all granted /Directory LocationMatch ^/git/.*/git-receive-pack$ AuthType Basic AuthName Git Repository AuthUserFile /etc/httpd/conf.d/git-users Require valid-user /LocationMatch LocationMatch ^/git/.*/git-upload-pack$ AuthType Basic AuthName Git Repository AuthUserFile /etc/httpd/conf.d/git-users Require valid-user /LocationMatch逐行解释一下关键配置。GIT_PROJECT_ROOT指定仓库的根目录所有仓库都放在这个目录下面。GIT_HTTP_EXPORT_ALL这个环境变量很重要没有它的话每个仓库目录下都需要放一个git-daemon-export-ok文件才能被 HTTP 访问加上它就省去这个麻烦。ScriptAlias把/git/开头的 URL 映射到git-http-backend程序。两个LocationMatch块分别针对推送和拉取操作做认证。这里有个容易踩的坑如果你只配了git-receive-pack的认证而没配git-upload-pack那匿名用户就能拉取代码只有推送才需要密码。反过来如果两个都配了那拉取和推送都要认证。根据你的安全需求选择我一般两个都配上统一管理。3.3 创建用户认证文件用htpasswd工具创建密码文件这个工具在httpd-tools包里装 httpd 的时候一般会一起装上。如果没有单独装一下dnf install -y httpd-tools创建第一个用户htpasswd -c /etc/httpd/conf.d/git-users alice系统会提示你输入两次密码。-c参数表示创建新文件第二次添加用户时就不要加-c了否则会把之前的用户覆盖掉htpasswd /etc/httpd/conf.d/git-users bob查看当前有哪些用户cat /etc/httpd/conf.d/git-users文件里存的是用户名和加密后的密码格式是用户名:加密串。这个文件要保证 Apache 能读到权限设为 640属主 root、属组 apachechown root:apache /etc/httpd/conf.d/git-users chmod 640 /etc/httpd/conf.d/git-users提示htpasswd默认使用 MD5 加密apr1 算法对于内网环境够用了。如果你对安全性要求更高可以加-B参数使用 bcrypt 加密但需要 Apache 2.4.4 以上版本支持。4. 仓库目录规划与权限体系搭建4.1 仓库根目录的创建与属主设置仓库根目录我选在/var/git/repositories这个路径和前面 Apache 配置里的GIT_PROJECT_ROOT对应。创建目录并设置属主mkdir -p /var/git/repositories chown -R apache:apache /var/git chmod -R 755 /var/git为什么属主是apache因为 Apache 默认以apache用户运行git-http-backend作为 CGI 程序也是以这个身份执行的。如果仓库目录属主是 rootApache 就没有写权限推送操作会直接失败报错信息通常是insufficient permission for adding an object to repository database。这个坑我踩过不止一次记住仓库目录必须让 apache 用户可写。4.2 初始化裸仓库的两种方式Git 服务端存放的必须是裸仓库也就是没有工作区的仓库。有两种创建方式。第一种在服务器上直接初始化cd /var/git/repositories git init --bare project-a.git chown -R apache:apache project-a.git第二种如果你本地已经有一个非裸仓库想把它搬到服务器上用--bare参数克隆一份git clone --bare /path/to/local/repo project-a.git裸仓库的目录结构和普通仓库不同它没有.git子目录仓库内容直接放在根目录下你会看到HEAD、config、objects、refs这些文件和目录。这是正常的不要觉得少了什么。创建完之后建议设置一个默认分支名。Git 2.28 之后可以用init.defaultBranch配置但更直接的方式是进到仓库里改 HEAD 文件cd /var/git/repositories/project-a.git git symbolic-ref HEAD refs/heads/main这样客户端克隆下来默认就在main分支上而不是传统的master。4.3 多用户权限的实用方案前面用htpasswd创建的用户只是通过了 Apache 的认证但 Git 层面并没有区分谁是谁。默认情况下所有通过认证的用户对仓库都有读写权限。对于小团队这通常够用但如果你想做更细粒度的控制有几种思路。最简单的做法是利用文件系统权限。为每个仓库创建一个对应的系统组把需要访问的用户加进去然后设置仓库目录的属组和权限groupadd git-project-a usermod -aG git-project-a alice chown -R apache:git-project-a /var/git/repositories/project-a.git chmod -R 770 /var/git/repositories/project-a.git但这里有个问题Apache 是以apache用户运行的它并不在git-project-a组里所以还是访问不了。解决办法是把apache用户也加进这个组usermod -aG git-project-a apache然后重启 Apache 让组变更生效。这种方案适合仓库数量不多、权限划分不复杂的场景。如果仓库多了手工维护组和权限会很累那就需要考虑上 GitLab 或者 Gitea 这类带完整权限管理的工具了。注意修改用户组之后已经运行的 Apache 进程不会自动获得新组权限必须systemctl restart httpd重启服务。这个细节很容易被忽略导致你改了权限却怎么都不生效。5. 从零跑通一次完整的克隆与推送5.1 启动服务并做连通性检查配置写完了先检查语法有没有问题apachectl configtest如果输出Syntax OK就可以启动服务了systemctl start httpd systemctl enable httpd确认服务在监听 80 端口ss -tlnp | grep :80然后在服务器本机用 curl 测试一下 Git 的智能 HTTP 端点是否正常响应curl -u alice http://localhost/git/project-a.git/info/refs?servicegit-upload-pack如果返回一串以001e# servicegit-upload-pack开头的内容说明后端已经通了。如果返回 403 或者 500那就得回头看 Apache 的错误日志tail -f /var/log/httpd/error_log日志里通常会明确告诉你问题在哪比如权限不足、CGI 执行失败、认证文件读取不到等等。5.2 客户端克隆与首次推送换到另一台机器上或者就在宿主机上执行克隆git clone http://alice192.168.1.100/git/project-a.git系统会提示输入密码输入正确后就开始下载。如果仓库是空的会提示warning: You appear to have cloned an empty repository.这是正常的。进入克隆下来的目录做一次提交然后推送cd project-a echo # Project A README.md git add README.md git commit -m initial commit git push origin main推送的时候会再次要求输入密码。如果一切正常你会看到类似Writing objects: 100%的输出说明推送成功。这里有个体验优化点每次推送都要输密码很烦。可以在客户端配置凭证缓存git config --global credential.helper cache这样密码会在内存里缓存 15 分钟。如果想永久保存可以用store模式但密码会以明文存在~/.git-credentials文件里安全性自己权衡。5.3 验证仓库数据的完整性推送完成后回到服务器上检查裸仓库里是否真的有数据了cd /var/git/repositories/project-a.git git log --oneline如果能看到你刚才提交的记录说明数据已经正确写入。再用git fsck检查一下仓库完整性git fsck --full没有报错就说明仓库状态健康。这一步很多人会跳过但在排查推送成功却拉不到代码这类诡异问题时git fsck往往能给出关键线索。6. 那些让我折腾半天的坑与排查思路6.1 推送时报 403 但拉取正常这个问题的典型表现是 clone 没问题一 push 就报error: The requested URL returned error: 403。原因几乎可以肯定是 Apache 配置里只给git-upload-pack配了认证没给git-receive-pack配或者反过来。检查git.conf里两个LocationMatch块是否都写了路径拼写是否正确。还有一种可能是GIT_HTTP_EXPORT_ALL没设置。没有这个环境变量git-http-backend会检查仓库目录下是否存在git-daemon-export-ok文件不存在就拒绝服务。这个报错在日志里体现为No such file or directory或者access denied。6.2 仓库目录权限导致的 500 错误Apache 错误日志里出现Permission denied并且伴随git-http-backend字样基本就是权限问题。排查顺序是先看仓库根目录和具体仓库目录的属主是不是apache再看目录权限有没有写位最后确认 SELinux 是否在拦截。SELinux 的问题比较隐蔽ls -Z查看目录的 SELinux 上下文如果显示unconfined_u:object_r:default_t之类的说明没有正确的httpd_sys_content_t或httpd_sys_rw_content_t标签。临时解决用chcon -R -t httpd_sys_rw_content_t /var/git永久解决用semanage fcontext加restorecon。6.3 大仓库推送中断的处理推一个几百 MB 的仓库时如果中途网络抖动或者超时可能会报RPC failed; HTTP 411或者fatal: the remote end hung up unexpectedly。这通常是 Apache 或 Git 的缓冲区限制导致的。在 Apache 配置里加一行SetEnv GIT_HTTP_MAX_REQUEST_BUFFER 100M同时在客户端调大 post 缓冲区git config http.postBuffer 524288000另外检查 Apache 的LimitRequestBody设置默认是 0 表示不限制但有些发行版会设成比较小的值需要确认一下。6.4 中文仓库名或路径的编码问题如果仓库名包含中文克隆时可能会遇到 404。这是因为 URL 编码和文件系统编码不一致导致的。我的建议是仓库名一律用英文小写加连字符比如project-alpha别用中文或者空格。这不是技术上限问题而是省去大量编码转换的麻烦团队协作时也更规范。7. 日常维护与几个提效小技巧7.1 仓库备份的简单可靠方案自建服务最怕数据丢。Git 仓库的备份其实很简单因为裸仓库本身就是一堆文件直接打包复制就行。我习惯用git clone --mirror做增量备份git clone --mirror /var/git/repositories/project-a.git /backup/project-a.git--mirror会完整复制所有引用包括分支、标签、远程跟踪分支。下次备份时进入备份目录执行git remote update就能增量同步。配合 cron 每天跑一次基本不用担心数据丢失。7.2 用 gitweb 给仓库加个网页界面命令行用久了有时候想快速看看某个仓库的提交历史开浏览器比敲命令方便。CentOS 8 仓库里有gitweb包装上之后配置一下就能用dnf install -y gitweb配置文件在/etc/gitweb.conf把$projectroot指向/var/git/repositories然后在 Apache 里加一个 alias 指向/var/www/git/目录。具体配置这里不展开思路就是让 gitweb 的 CGI 脚本能读到仓库列表。配好之后访问http://服务器地址/gitweb/就能看到所有仓库的网页视图提交历史、文件树、diff 都能看。7.3 监控仓库增长与清理策略Git 仓库只会越来越大尤其是二进制文件多的项目。定期用git count-objects -vH查看仓库大小如果发现size-pack异常大可能是有人误提交了大文件。处理方式是先用git filter-repo或者BFG清理历史然后让所有客户端重新克隆。预防措施是在仓库根目录放一个.gitignore模板把常见的构建产物、依赖目录、日志文件都排除掉。另外可以设置gc.auto参数控制自动垃圾回收的阈值默认是 6700 个松散对象触发一次 gc。对于活跃仓库可以调低这个值让 gc 更频繁地运行避免对象堆积过多影响性能git config --global gc.auto 2000这套 HTTP 协议的 Git 服务搭下来从虚拟机准备到跑通推送顺利的话一个小时内能搞定。真正花时间的是排错而排错的关键在于看懂 Apache 的错误日志和 Git 的报错信息。我自己的经验是遇到问题先看日志日志里 90% 的情况会直接告诉你原因剩下 10% 需要你理解 git-http-backend 的工作链路去推断。把这套环境跑通一次之后后面再搭就是复制粘贴的事换个仓库名、改个端口十分钟就能再起一个。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。