Home / Alt manpages / git-daemon(1)

  • git-daemon(1)
  • User command
  • linux

Serve Read-Only Git Repositories with git daemon

You will finish with a small Git protocol service that exports selected repositories over git://, then verify it from a client. The example uses a temporary directory, port 19418, and Git 2.43.0 from the installed git-man package 1:2.43.0-1ubuntu7.3.

Allow about fifteen minutes. You need Git on the server and a shell on a client, or both roles on one test machine. The commands below do not need elevated privileges because they use a temporary directory and a port above 1024. A real service on port 9418 may need service supervision, firewall changes, and a dedicated account; those are deployment decisions, not reasons to run the daemon as root.

1. Check the installed command

Confirm the version and the subcommand syntax before building the test service. This is an ordinary read-only check:

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

The installed manual describes git daemon as a simple TCP server, normally listening on port 9418. Its default service is upload-pack, which supports fetches, pulls, clones and git ls-remote. It is intended for read-only updates.

Checkpoint

Keep the version output. Option details can differ between Git releases, and this guide is written against the local 2.43.0 installation.

2. Create a repository and mark it for export

Make a bare repository inside a temporary export tree. A bare repository has the objects and refs directories that the daemon expects, but no working tree:

$ export GIT_DAEMON_ROOT="$(mktemp -d /tmp/git-daemon-demo.XXXXXX)"
$ mkdir -p "$GIT_DAEMON_ROOT/repos"
$ git init --bare "$GIT_DAEMON_ROOT/repos/project.git"
$ touch "$GIT_DAEMON_ROOT/repos/project.git/git-daemon-export-ok"
$ find "$GIT_DAEMON_ROOT/repos/project.git" -maxdepth 1 -type f -printf '%f\n' | sort
HEAD
config
description
git-daemon-export-ok

The marker file is the important boundary. Without git-daemon-export-ok, the normal daemon refuses to export the repository. That default is useful: creating a repository under an export root does not publish it by accident.

This example has changed state under /tmp. When you have finished testing, remove the temporary tree only after checking the variable points at the expected path:

$ printf 'temporary tree: %s\n' "$GIT_DAEMON_ROOT"
$ test -n "$GIT_DAEMON_ROOT" && case "$GIT_DAEMON_ROOT" in /tmp/git-daemon-demo.*) echo 'path looks correct' ;; *) echo 'refusing cleanup' >&2; exit 1 ;; esac
$ rm -rf -- "$GIT_DAEMON_ROOT"
$ unset GIT_DAEMON_ROOT

Warning

The final command is destructive for that temporary tree. Do not adapt it to a broad directory or to a production repository.

3. Start a constrained foreground daemon

Start the daemon in the foreground so its log stays visible and the process is easy to stop with Ctrl-C. The base path maps a client request for project.git to the repository below it. The directory argument limits which paths can be served:

$ git daemon --reuseaddr --verbose \
    --base-path="$GIT_DAEMON_ROOT/repos" \
    --port=19418 "$GIT_DAEMON_ROOT/repos"
[PID] Ready to rumble

Leave this terminal running. --verbose reports connections and requested files. --reuseaddr permits a quick restart after a stop. The custom port avoids competing with another Git daemon and avoids privileged ports. The daemon still serves only repositories that are marked for export.

Open a second terminal and recreate the variable there with the actual printed path, for example:

$ export GIT_DAEMON_ROOT='/tmp/git-daemon-demo.A1b2C3'
$ test -d "$GIT_DAEMON_ROOT/repos/project.git"
$ git ls-remote "git://127.0.0.1:19418/project.git"
$ printf 'client status: %s\n' "$?"
client status: 0

An empty ls-remote result is normal for a newly created bare repository with no refs. The status is the useful part. In the daemon terminal, expect a request mentioning upload-pack and /project.git. The exact process IDs and connection ports vary.

4. Prove the export boundary

Stop the foreground daemon with Ctrl-C, remove the marker from the test repository, and start the same command again. This is a deliberate temporary change to demonstrate the default access check:

$ rm -- "$GIT_DAEMON_ROOT/repos/project.git/git-daemon-export-ok"
$ git daemon --reuseaddr --verbose \
    --base-path="$GIT_DAEMON_ROOT/repos" \
    --port=19418 "$GIT_DAEMON_ROOT/repos"
$ git ls-remote "git://127.0.0.1:19418/project.git"
fatal: remote error: access denied or repository not exported: /project.git

The wording may vary with the client, but the request should fail. Restore the marker before continuing:

$ touch "$GIT_DAEMON_ROOT/repos/project.git/git-daemon-export-ok"
$ git ls-remote "git://127.0.0.1:19418/project.git"
$ printf 'restored status: %s\n' "$?"
restored status: 0

The daemon's default error is intentionally vague. --informative-errors gives clients more detail, but can reveal whether an unexported repository exists. Leave that option off on a service where repository-name disclosure matters.

5. Keep the service read-only

Do not enable receive-pack for an open or mixed-trust network. The manual says it permits anonymous pushes and has no authentication, so anyone who can reach the service could push or remove refs. It is suitable only for a closed LAN where that risk is accepted. The default is disabled.

upload-pack remains enabled by default. upload-archive, which serves remote git archive requests, is disabled by default too. If you enable it for a particular repository, put the setting in that repository's config file:

[daemon]
        uploadarch = true

That is a deliberate configuration change. Record why it is needed, test it from an untrusted client, and undo it by removing the setting or changing it to false. Do not use --export-all casually either: it bypasses the per-repository marker and exports every directory that looks like a Git repository under the daemon's allowed paths.

6. Choose a production boundary

For a real read-only service, prefer an explicit repository root and a dedicated unprivileged account. --base-path=/srv/git makes a request for team/project.git resolve under /srv/git/team/project.git. Add --strict-paths when requests must match the supplied directory arguments exactly; note that it refuses to start if no directory arguments are supplied.

--user=git and optional --group=git make the daemon switch identity before serving requests. The names are looked up through the system account databases, not treated as numeric IDs. If you use this option, ensure the account can read the repositories and remember that Git programs do not automatically receive a reset HOME environment. Set an appropriate HOME before starting the service if configuration in that account's home directory is required.

Use a service manager or inetd only after the foreground test works. --inetd is incompatible with --detach, --port, --listen, --user and --group. If you detach, logging defaults to syslog; for a first deployment, foreground logging or an explicitly chosen log destination is easier to diagnose.

Done means

  • Git 2.43.0 and the installed git-man package were checked.
  • The repository was exported only after creating git-daemon-export-ok.
  • git ls-remote reached the daemon and returned status 0.
  • Removing the marker caused the same request to fail, then restoring it fixed the request.
  • receive-pack and --export-all were left disabled.
  • The foreground daemon was stopped before cleaning the temporary tree.