Serving git over HTTP using git-http-backend
My original version of this article was all about the problems I had using
git-http-backend to make a git repository available over HTTP with
authentication on a Rocky Linux 10 system. Cloning
the repository was fine but git push would fail. I could get my setup
working on Slackware and Debian 13 and Rocky Linux 9, but not Rocky Linux 10
(or Almalinux 10). I assumed I was doing something wrong but was clueless as
to what.
Then I found this server-world.info article for Rocky Linux 10 — Git: Access to Repos via HTTP — which was exactly what I was trying to do. Clearly it was possible to make it work.
After fiddling about with what was different with my setup, I established that having the git repository configured to be shared amongst users was the source of the push failure. So the new purpose of this article is to document the error I encountered and how to resolve it in case someone else has the same problem.
The packages required on top of a minimal Rocky 10 install are httpd,
httpd-tools and git. As I want to use SSL as well, I also require
certbot and python3-certbot-apache.
See the git-http-backend man page and/or "Git on the Server" chapter of the
Pro Git book
for the details on configuring git for HTTP access.
Git repository setup
My repositories are located in /srv/git/ on the host. This directory and
its repositories must readable and writable by the web server’s user or group.
For RHEL-based distributions, this is "apache".
### On my local PC # Create a bare clone of the repository git clone --bare my_proj my_proj.git # Copy the bare repo to the remote host scp -r my_proj.git gsl@server.gslewis.id.au:my_proj ### On the remote host, as root # Move the repository to /srv/git mv ~gsl/my_proj /srv/git/ # Set group ownership and write permissions on all repos chgrp -R apache /srv/git/ chmod -R g+w /srv/git/
Alternatively you could set the user ownership on the repositories to the "apache" user, leaving the group ownership and permissions unchanged.
We also need to either disable SELinux or change the context of the
/srv/git/ directory so that it can be read from and written to by the web
server. I usually just disable SELinux according to the instructions in
/etc/selinux/config.
If you want to leave SELinux enabled, you can designate the /srv/git
directory as readable and writeable web server content using the semanage
utility from the the policycoreutils-python-utils package.
semanage fcontext -a -t httpd_sys_rw_content_t "/srv/git(/.*)?" restorecon -vR /srv/git/
To simplify matters, I also "disable" the git safe directory check by treating all repositories as safe, system-wide.
git config --system --add safe.directory "*"
This creates the /etc/gitconfig file if not already present.
Web server configuration
I am using Apache httpd as the web server with
digest authentication to restrict access. Based on the Pro Git chapter on
Smart HTTP, the
basic <VirtualHost> entry I created was:
<VirtualHost _default_:443>
ServerName https://server.gslewis.id.au
# A bunch of SSL-related directives for LetsEncrypt certs.
SetEnv GIT_PROJECT_ROOT /srv/git
SetEnv GIT_HTTP_EXPORT_ALL
SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0
ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
<Location /git>
AuthName "Git Access"
AuthType Digest
AuthUserFile /etc/httpd/auth/git-users.htdigest
Require valid-user
</Location>
</VirtualHost>
This configures git-http-backend as the CGI script handler for the URL path
/git/ which will match repositories in the /srv/git directory.
For each user I want to grant access to the repositories, I use htdigest to
populate the git-users.htdigest file.
# Create the htdigest file and add myself (gsl) htdigest -c /etc/httpd/auth/git-users.htdigest "Git Access" gsl
Using the repository
In theory, accessing git via HTTP should now work. With my repository at
/srv/git/my_proj, I can now clone it after being prompted for a username and
password from the htdigest file.
gsl@sula:~/workspace$ git clone https://server.gslewis.id.au/git/my_proj Cloning into 'my_proj'... Username for 'https://server.gslewis.id.au': gsl Password for 'https://gsl@server.gslewis.id.au': remote: Enumerating objects: 17, done. remote: Counting objects: 100% (17/17), done. remote: Compressing objects: 100% (11/11), done. remote: Total 17 (delta 8), reused 15 (delta 6), pack-reused 0 (from 0) Receiving objects: 100% (17/17), 6.30 KiB | 6.30 MiB/s, done. Resolving deltas: 100% (8/8), done.
All fine and dandy. Now make a change, commit it and push it up to the server. What I originally encountered was the following:
gsl@sula:~/workspace/my_proj$ git commit -a -m "Testing push" [master 5fb4dbb] Testing push 1 file changed, 1 insertion(+) gsl@sula:~/workspace/my_proj$ git push Username for 'https://server.gslewis.id.au': gsl Password for 'https://gsl@server.gslewis.id.au': Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 16 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 288 bytes | 288.00 KiB/s, done. Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0) remote: error: unable to create temporary file: Operation not permitted remote: fatal: failed to write object error: remote unpack failed: unpack-objects abnormal exit To https://server.gslewis.id.au/git/my_proj ! [remote rejected] master -> master (unpacker error) error: failed to push some refs to 'https://server.gslewis.id.au/git/my_proj'
Originally I had been considering using SSH to access the remote repository with separate user accounts and while testing that out, I had reinitialized the git repository on the server as a shared repository.
git init --bare --shared /srv/git/my_proj
When I followed the server-world.info guide and created a brand new empty bare
repository (new_proj), not shared, I was able to clone, add a file, and push
it to the server via HTTP without an error.
The config file for the fresh, non-shared repository was:
[core]
repositoryformatversion = 0
filemode = true
bare = true
Compare with the config file for my existing project, copied from my local
PC to the server and reinitialized as shared:
[core]
repositoryformatversion = 0
filemode = true
bare = true
sharedrepository = 1
[remote "origin"]
url = /home/gsl/workspace/my_proj
[receive]
denyNonFastforwards = true
With that git config, pushing failed. If I delete the "sharedrepository" line, pushing succeeds.
gsl@sula:~/workspace/my_proj$ git push Username for 'https://server.gslewis.id.au': gsl Password for 'https://gsl@server.gslewis.id.au': Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 16 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 290 bytes | 290.00 KiB/s, done. Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0) To https://server.gslewis.id.au/git/my_proj a5a454f..4cb74db master -> master
There may well be some combination of git configuration options, directory user/group ownership, and permissions that allows a git repository to receive pushes via both HTTP and shared SSH for multiple users, but I don’t know what it is. For now, I shall stick to using one or the other as the situation demands.
What is potentially noteworthy is that this problem with git-http-backend
and shared repositories only seems to exist on RHEL10-based distributions. I
have tried with Rocky Linux 10 and Almalinux 10 and both fail when the
"sharedrepository=1" setting is present in the git config. However Rocky
Linux 9, Debian 13, and Slackware all work fine with a shared repository.