Home / Alt manpages / git-http-backend(1)

  • git-http-backend(1)
  • User command
  • linux

Publish Git Repositories over HTTP with git-http-backend

You will configure Git's CGI backend behind a web server, expose repositories from a controlled directory, and verify read access with git clone or git ls-remote. The examples use Git 2.43.0 from git-man 1:2.43.0-1ubuntu7.3, which is the installed version on this machine. Allow about 30 minutes for a first configuration, plus time to reload the web server safely.

This guide assumes Apache 2.x, a working CGI setup, and repositories stored below /var/www/git. Adapt paths to your distribution. Web-server configuration needs elevated privileges; the client checks and repository configuration can normally run as an ordinary user.

1. Check the backend and choose the repository root

git-http-backend is a CGI program. It does not listen on a port by itself. The web server supplies CGI variables, including PATH_INFO, the request method and authentication information. When GIT_PROJECT_ROOT is set, the backend joins it with PATH_INFO to find the repository.

$ git --version
git version 2.43.0
$ git --exec-path
/usr/lib/git-core
$ test -x "$(git --exec-path)/git-http-backend" && echo backend-ok
backend-ok

The exact backend path varies by package. Use $(git --exec-path)/git-http-backend in a shell, or expand it to the path returned by git --exec-path, instead of assuming the example path. Check the package version too if you are comparing behaviour across hosts:

$ dpkg-query -W -f='\${Package} \${Version}\n' git-man
git-man 1:2.43.0-1ubuntu7.3

Checkpoint: write down one URL and its intended disk path. For example, https://git.example.test/git/team/app.git should map to /var/www/git/team/app.git. Do not let the public URL point at an arbitrary document root containing unrelated files.

2. Prepare an exportable repository

Create or place a bare repository under the chosen root. This changes the server's repository storage, so take a backup or use a new test repository before working on a live service.

$ sudo install -d -o www-data -g www-data /var/www/git/team/app.git
$ sudo git init --bare /var/www/git/team/app.git
$ sudo touch /var/www/git/team/app.git/git-daemon-export-ok
$ sudo chown www-data:www-data /var/www/git/team/app.git/git-daemon-export-ok

The marker file is the default export boundary. The backend refuses to export a Git directory without it. You can instead set GIT_HTTP_EXPORT_ALL in the web server, but that removes this per-repository check. Do not enable that broad bypass until every repository below the root is meant to be public.

For an existing repository, verify that the path is the one you intend to publish:

$ sudo test -d /var/www/git/team/app.git && echo repository-directory-ok
repository-directory-ok
$ sudo test -f /var/www/git/team/app.git/git-daemon-export-ok && echo export-marker-ok
export-marker-ok

3. Map the URL to the CGI

Enable the Apache modules required by the documented example, then add a small virtual-host fragment. The following is configuration data for Apache, not a shell command. Replace /usr/lib/git-core/git-http-backend/ with the path from step 1 if necessary.

SetEnv GIT_PROJECT_ROOT /var/www/git
ScriptAlias /git/ /usr/lib/git-core/git-http-backend/

# Preserve Git protocol negotiation when the server does not copy it by default.
SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=\$0

The trailing slash in the ScriptAlias is significant for the URL shape shown here. The web server must pass the part after /git/ as PATH_INFO. If your Apache layout supplies PATH_TRANSLATED instead, the backend can use that when GIT_PROJECT_ROOT is absent, but using an explicit project root makes the mapping easier to audit.

Before reloading a running service, test the configuration. These commands require elevated privileges because they read the server configuration and may reload it:

$ sudo apachectl configtest
Syntax OK
$ sudo systemctl reload apache2

A reload is service-disrupting on some installations even when it is normally graceful. Keep the previous configuration available. To undo this step, remove the fragment, run sudo apachectl configtest again, and reload the last known-good configuration.

4. Test anonymous read access

Use git ls-remote first because it reads refs without creating a working copy. This is an ordinary client command:

$ git ls-remote https://git.example.test/git/team/app.git
4f2c1e8b...	HEAD
4f2c1e8b...	refs/heads/main

The object IDs and branch names will differ. A successful response confirms the URL mapping, export permission and the default upload-pack service. If you get HTTP 404, inspect the ScriptAlias, URL spelling and the backend path. If you get HTTP 403, check the export marker and any web-server access rules. Do not add GIT_HTTP_EXPORT_ALL merely to hide an incorrect path.

Now test an actual clone into a new directory:

$ git clone https://git.example.test/git/team/app.git app-check
Cloning into 'app-check'...

Remove only this disposable checkout after checking it. This is destructive for the checkout directory, not for the server repository:

$ rm -rf -- app-check

5. Add authenticated push access deliberately

Push support is a separate security decision. In the installed Git 2.43.0 behaviour, receive-pack is disabled for anonymous users by default and enabled by default for users authenticated by the web server. Configure authentication at the web-server layer, using TLS and your existing identity system. Do not put passwords in this file or in a Git URL.

For a private repository, protect the whole URL space so reads and writes require authentication:

<Location /git/private>
    AuthType Basic
    AuthName "Private Git Access"
    Require group committers
</Location>

The authentication provider and group definition are site-specific. After reloading with the same configuration test, verify both operations using a test account. A read-only account should pass git ls-remote but should not be allowed to push.

For anonymous reads and authenticated writes, protect both the initial receive-pack advertisement and the receive-pack request itself. The exact Apache rules depend on the modules and authentication setup. If only the latter is protected, the client may receive 403 Forbidden before it gets an opportunity to authenticate unless http.receivepack is enabled for repositories that should accept pushes:

$ sudo git -C /var/www/git/team/app.git config http.receivepack true
$ sudo git -C /var/www/git/team/app.git config --get http.receivepack
true

Do not set this option for a repository until the web server reliably authenticates and authorises writers. To undo it, run sudo git -C /var/www/git/team/app.git config --unset http.receivepack; the default then applies again.

6. Check the service boundaries and large ref sets

http.uploadpack controls fetch and ref advertisement and is enabled by default. http.receivepack controls pushes. http.getanyfile supports older clients and can expose any file in the repository, including unreachable objects, so disable it when those old clients are not required:

$ sudo git -C /var/www/git/team/app.git config http.getanyfile false
$ sudo git -C /var/www/git/team/app.git config --get-regexp '^http\.'
http.getanyfile false

The backend's ref negotiation request buffer defaults to 10 megabytes. A repository with an unusually large number of refs may need a larger http.maxRequestBuffer, or the equivalent GIT_HTTP_MAX_REQUEST_BUFFER environment variable. Raise it only after measuring a real fetch failure, because it increases the request size the service will handle.

Protocol v2 negotiation depends on the client Git-Protocol header reaching the CGI as GIT_PROTOCOL. The Apache rule in step 3 makes that explicit for deployments where automatic header copying is not enough.

Done means

  • The installed backend version and executable path are recorded.
  • The URL-to-disk mapping is limited to the intended Git project root.
  • Each anonymously readable repository has an explicit export marker, unless a deliberate global policy replaces it.
  • git ls-remote and a disposable git clone succeed over HTTPS.
  • Push access is authenticated, authorised and tested separately from anonymous reads.
  • Old-client file access and unusually large ref sets have been considered rather than enabled blindly.