gslewis.id.au:~
The Bedlington Ape

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:

/etc/httpd/conf.d/default.conf
 <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.

Clone the repository over HTTPS
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:

Error when pushing a change back to the server
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:

/srv/git/new_proj/config
[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:

/srv/git/my_proj/config
[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.

Success when pushing a change back to the server
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.

The Bedlington Ape Back to the home page