How to run a Git server on GNU/Linux?

How to run a Git server on GNU/Linux?

Published on: March 11, 2024

If you want to set up your own version control for a project, but prefer not to host it on a Git hosting service (like GitHub), you can run your own Git server to store your code and act as a central repository for all of collaborators.

Why host your own Git server?

You may run your own Git server, if you don't want to store your code on someone else's servers. You may need to have full control of your version control infrastructure.

Also, if you're using a Git hosting service, there are some restrictions that may not be ideal. For example, GitHub doesn't allow files above 100 MB, which may be a critical problem for projects with large files. Running your own Git server may allow these larger files.

Initializing Git repositories

First off, you need to install Git. To do that, you can run sudo apt install git on Debian-based systems (Debian, Devuan, Ubuntu, Linux Mint), sudo dnf install git or sudo yum install git on RHEL-based systems (Fedora, CentOS, RHEL), sudo zypper install git-core on SUSE-based systems (SUSE Linux Enterprise, openSUSE) or sudo pacman -S git on Arch-based systems (Arch Linux, Manjaro).

Next, add repositories to a Git root directory. In our case we're using /var/lib/git directory. For our scenario, we're just making a clone of SVR.JS Git repository:

sudo mkdir /var/lib/git
cd /var/lib/git
sudo git clone --bare https://git.svrjs.org/svrjs.git  #You can also clone any other Git repository or just create a new blank repository using `git init --bare myrepo.git`

You may need to add git-daemon-export-ok to mark Git repositories as safe for export:

sudo find /var/lib/git -mindepth 1 -maxdepth 1 -exec touch {}/git-daemon-export-ok \;

If you want to learn more about Git commands, you can check out our post about Git commands.

Then create a new Git user (we're using /var/lib/gituser directory as a user directory):

sudo useradd -s /usr/bin/git-shell -d /var/lib/gituser -m -r git

And change the permissions for the Git root directory and Git user directory:

sudo chown -hR git:git /var/lib/git
sudo chmod -R ug+rw /var/lib/git
sudo chown -hR git:git /var/lib/gituser
sudo chmod -R g-w /var/lib/gituser

Finally, change the default file creation mask by adding or replacing the UMASK line in the /etc/login.defs file:

UMASK 002

The initial setup of a Git server

The initial setup of a Git server

Now you are ready to serve Git to the public through one of three protocols:

  • Git protocol
  • SSH
  • HTTP(S)

Hosting through Git protocol

Git protocol is a protocol used by Git daemon that comes packaged with Git; it listens on a dedicated port (9418) that provides a service similar to one using SSH protocol, but it has absolutely no authenttication or cryptography. That means no uploading files by default. But Git protocol is very efficient.

To set up Git daemon, first install the Git daemon. You can use sudo apt install git-daemon-sysvinit (SystemV init or systemd) or sudo apt install git-daemon-run (runit) on Debian-based systems (Debian, Devuan, Ubuntu, Linux Mint), sudo dnf install git-daemon or sudo yum install git-daemon on RHEL-based systems (Fedora, CentOS, RHEL), sudo zypper install git-daemon on SUSE-based systems (SUSE Linux Enterprise, openSUSE). Users of Arch-based systems (Arch Linux, Manjaro) don't need to install an additional package.

Then change the /etc/default/git-daemon file contents to:

GIT_DAEMON_ENABLE=true
GIT_DAEMON_USER=git
GIT_DAEMON_BASE_PATH=/var/lib/git
GIT_DAEMON_DIRECTORY=/var/lib/git
GIT_DAEMON_OPTIONS=""

You may change GIT_DAEMON_OPTIONS to enable git push, however anybody can do git push and do unauthorized changes:

#WARNING!!! INSECURE!!!
GIT_DAEMON_OPTIONS="--enable=receive-pack"

Finally, restart the daemon with sudo systemctl restart git-daemon or sudo /etc/init.d/git-daemon restart.

If you're using a ufw firewall, you can use:

sudo ufw allow git

The Git protocol setup of a Git server

The Git protocol setup of a Git server

You can test the server by running this command on a client (assuming that the server has 10.0.0.2 address):

git clone git://10.0.0.2/svrjs.git
#Replace `svrjs.git` with repository name and `10.0.0.2` with your server address.

Test of the Git protocol setup

Test of the Git protocol setup

Now you have set up a Git server serving through Git protocol!

Hosting through SSH

SSH protocol is a more common protocol for Git servers. This is because SSH access is already set up in many server configurations, and if it isn't, it's easy to set it up. SSH is also authenticated, and it's generally easy to set up and use due to it being ubiquitous.

If you don't have SSH server, you can install it (in this case we're using OpenSSH). To do that, you can run sudo apt install openssh-server on Debian-based systems (Debian, Devuan, Ubuntu, Linux Mint), sudo dnf install openssh-server or sudo yum install openssh-server on RHEL-based systems (Fedora, CentOS, RHEL), sudo zypper install openssh on SUSE-based systems (SUSE Linux Enterprise, openSUSE) or sudo pacman -S openssh on Arch-based systems (Arch Linux, Manjaro).

If you're using a ufw firewall, you can use:

sudo ufw allow ssh

You can either use password or public key for the authentication.

If you want to use password, you can set it using this command (run it on server):

sudo passwd git

If you want to use public keys, first set up a password for the user. Then you can temporary switch the shell of git user to sh (run it on server):

sudo usermod -s /bin/sh git

Then you can generate the SSH keys and send it to the server (assuming that 10.0.0.2 is a server address; run it on client):

ssh-keygen -o
ssh-copy-id git@10.0.0.2
#Replace `10.0.0.2` with your server address.

Preparing the SSH keys...

Preparing the SSH keys...

Finally you can revert the shell change (run it on server):

sudo usermod -s /usr/bin/git-shell git

If you want to disable password authentication, you can add this to your /etc/ssh/sshd_config file:

Match User git
	PasswordAuthentication No

And restart the server using either sudo systemctl restart ssh or sudo /etc/init.d/ssh restart.

You may also create a chroot environment for a Git server.

The SSH setup of a Git server

The SSH setup of a Git server

You can test the server by running this command on a client (assuming that the server has 10.0.0.2 address):

git clone ssh://git@10.0.0.2/var/lib/git/svrjs.git
#Replace `svrjs.git` with repository name and `10.0.0.2` with your server address.
#When using chroot, remove the `/var/lib/git` part.

Test of the SSH setup

Test of the SSH setup

Now you have set up a Git server serving through SSH protocol!

Hosting through HTTP (along with GitWeb)

HTTP protocol is also a very common protocol for Git servers. You can use HTTP authentication to protect Git server from unauthorized git push operations (if there is no HTTP authentication, git push operations are disabled by default). You can also set up to anonymously serve Git repositories for cloning.

For Git hosting services (like GitHub), the cloning URL is the same as the URL you use to view the repository through the web browser. You will also set up this configuration.

For this purpose, we're using GitWeb as a Git repository viewer and git-http-backend for the Git cloning.

That means that you can use any web server software, that supports CGI (Common Gateway Interface), since both GitWeb and git-http-backend use CGI.

To set up GitWeb, first install the GitWeb. You can use sudo apt install gitweb on Debian-based systems (Debian, Devuan, Ubuntu, Linux Mint), sudo dnf install gitweb or sudo yum install gitweb on RHEL-based systems (Fedora, CentOS, RHEL), sudo zypper install git-web on SUSE-based systems (SUSE Linux Enterprise, openSUSE). Users of Arch-based systems (Arch Linux, Manjaro) may need to install Perl CGI module (a dependency of GitWeb) to use GitWeb using sudo pacman -S perl-cgi.

For the web server, we're using SVR.JS web server with RedBrick mod. First download SVR.JS web server and SVR.JS installer for GNU/Linux. You can use these commands to install SVR.JS:

cd ~
wget https://downloads.svrjs.org/svr.js.3.14.5.zip #Replace "3.14.5" with the latest SVR.JS version you can get from the SVR.JS website
wget https://downloads.svrjs.org/installer/https://downloads.svrjs.org/installer/svr.js.installer.linux.20240219.zip #You can replace "svr.js.installer.linux.20240219.zip" with latest installer archive you can find on the "installer" directory
mkdir installer
cd installer
unzip ../svr.js.installer.linux.20240219.zip
cp ../svr.js.3.14.5.zip svrjs.zip
sudo bash installer.sh

After installing SVR.JS, start the server using sudo systemctl start svrjs or sudo /etc/init.d/svrjs start.

SVR.JS default page

SVR.JS default page

Later, run these commands to avoid permission conflict with SSH- and Git protocol-based Git services (for web servers other than SVR.JS installed with SVR.JS installer, replace svrjs with www-data):

sudo usermod -aG svrjs git
sudo usermod -aG git svrjs

If you're using a ufw firewall, you can use:

sudo ufw allow http

For other web servers, you can read the git-http-backend documentation and the GitWeb documentation.

Now after installing SVR.JS you can delete everything under the /var/www/svrjs directory (sudo rm -rf /var/www/svrjs/*) and restart the server using sudo systemctl restart svrjs or sudo /etc/init.d/svrjs restart.

After the deletion, the only thing that's left is the empty directory listing, when you visit http://10.0.0.2/ (assuming that the server address is 10.0.0.2):

Empty directory listing on the HTTP server

Empty directory listing on the HTTP server

Since SVR.JS itself doesn't have CGI support, you need to install the RedBrick mod. RedBrick basically adds CGI support for the SVR.JS web server. To do that, you can go to the SVR.JS mods page and run the commands:

cd /usr/lib/svrjs/mods
sudo wget https://downloads.svrjs.org/mods/redbrick.cgi.2.6.0.tar.gz #Replace "2.6.0" with the latest version of the RedBrick found on the SVR.JS mods page.

After installing the mod, restart the server using sudo systemctl restart svrjs or sudo /etc/init.d/svrjs restart.

Then add files under the cgi-bin directory on the web root:

cd /var/www/svrjs
sudo ln -s /usr/lib/cgi-bin cgi-bin
sudo ln -s /usr/share/gitweb/static .
cd cgi-bin
sudo ln -s /usr/share/gitweb/gitweb.cgi .
sudo ln -s /usr/share/gitweb/static .
sudo ln -s /usr/lib/git-core/git-http-backend git.cgi

After adding the files, stop the SVR.JS service using sudo systemctl stop svrjs or sudo /etc/init.d/svrjs stop and change the SVR.JS configuration (in /etc/svrjs-config.json or /usr/lib/svrjs/config.json) like this:

{
  "nonStandardCodes": [
    {
      "scode": 301,
      "regex": "/^\\/git\\/(.*)/",
      "location": "/$1"
    },
    {
      "scode": 301,
      "regex": "/^\\/git($|[?#].*)/",
      "location": "/$1"
    },
    {
      "scode": 401,
      "realm": "Git Access",
      "regex": "/^\\/cgi-bin\\/git\\.cgi(?:(\\/.*)?\\/git-receive-pack(?:$|[?#])|(?:\\/[^?]*)?\\?(?:[^#]*[;&?]|)service=git-receive-pack(?:$|[;&?#]))/"
    }
  ],
  "allowStatus": true,
  "enableCompression": true,
  "customHeaders": {},
  "enableLogging": true,
  "enableDirectoryListing": false,
  "enableDirectoryListingWithDefaultHead": false,
  "stackHidden": true,
  "enableRemoteLogBrowsing": false,
  "exposeServerVersion": false,
  "disableServerSideScriptExpose": true,
  "rewriteMap": [
    {
      "definingRegex": "/^\\/(?!git(?:$|[?\\/#])|svrjsstatus\\.svr(?:$|[?#]))(?![^\\/]+\\/(?:branches|hooks|info|logs|objects|refs|config|description|HEAD|git-upload-pack|git-receive-pack)(?:$|[?\\/#]))/",
      "isNotFile": true,
      "replacements": [
        {
          "regex": "/^\\//",
          "replacement": "/cgi-bin/gitweb.cgi/"
        }
      ]
    },
    {
      "definingRegex": "/^\\/(?!git(?:$|[?\\/#])|svrjsstatus\\.svr(?:$|[?#]))/",
      "isNotFile": true,
      "replacements": [
        {
          "regex": "/^\\//",
          "replacement": "/cgi-bin/git.cgi/"
        }
      ]
    }
  ],
  "dontCompress": [
    "/.*\\.ipxe$/",
    "/.*\\.img$/",
    "/.*\\.iso$/",
    "/.*\\.png$/",
    "/.*\\.woff$/"
  ],
  "enableIPSpoofing": false,
  "exposeModsInErrorPages": false,
  "enableETag": true,
  "disableUnusedWorkerTermination": false,
  "rewriteDirtyURLs": false,
  "wwwroot": "/var/www/svrjs",
  "disableTrailingSlashRedirects": false,
  "environmentVariables": {
    "GIT_PROJECT_ROOT": "/var/lib/git"
  },
  "customHeaders": {
    "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
    "X-Frame-Options": "sameorigin",
    "X-Content-Type-Options": "nosniff",
    "Content-Security-Policy": "default-src 'self'; object-src 'none'; script-src 'self' 'sha256-dacEZQWGxky95ybZadcNI26RDghVLeVdbdRC/Q3spJQ='; img-src 'self' www.gravatar.com data:"
  },
  "allowDoubleSlashes": false
}

Change the enableIPSpoofing property to true, if your server is behind a reverse proxy. For more configuration options, see the SVR.JS documentation.

After the configuration changes, you can add the git user to the HTTP server:

sudo svrpasswd -a git

Later, modify the /etc/gitweb.conf file (GitWeb configuarion) like this:

$projectroot = "/var/lib/git";
$git_temp = "/tmp";
@stylesheets = ("/static/gitweb.css");
$javascript = "/static/gitweb.js";
$logo = "/static/git-logo.js";
$favicon = "/static/git-favicon.js";
$feature{'pathinfo'}{'default'} = [1];
@diff_opts = ();

For more configuration options, see the gitweb.conf documentation.

After those modifications, start the server using sudo systemctl start svrjs or sudo /etc/init.d/svrjs start.

You have now both Git server and GitWeb running!

The HTTP setup of a Git server

The HTTP setup of a Git server

You can test the server by running this command on a client (assuming that the server has 10.0.0.2 address):

git clone http://10.0.0.2/svrjs.git
#Replace `svrjs.git` with repository name and `10.0.0.2` with your server address.

Test of the HTTP setup

Test of the HTTP setup

You can also test out GitWeb by visiting http://10.0.0.2/ (assuming that the server address is 10.0.0.2):

The GitWeb view of the svrjs.git repository

The GitWeb view of the svrjs.git repository

You may notice this description: Unnamed repository; edit this file 'description' to name the repository. In this case you can change it by editing the description file in the Git repository directory.

Adding HTTP encryption

You have probably set up service per instructions above, but there may be one problem: there is no encryption! You may need encryption to ensure that no one is eavesdropping for your Git data and credentials you use to push the repositories.

To do that in SVR.JS, first obtain a TLS certificate from a certificate authority (you can obtain Let's Encrypt certificates for free using certbot).

After obtaining a TLS certificate, copy the certificate and the private key, so that it is accessible in those paths:

  • /usr/lib/svrjs/cert/cert.crt - TLS certificate
  • /usr/lib/svrjs/cert/key.key - private key

After copying the files, stop the SVR.JS service using sudo systemctl stop svrjs or sudo /etc/init.d/svrjs stop and append to the SVR.JS configuration (in /etc/svrjs-config.json or /usr/lib/svrjs/config.json) like this:

  ...
  "allowDoubleSlashes": false,
  "secure": true,
  "cert": "cert/cert.crt",
  "key": "cert/key.key"
}

After those modifications, start the server using sudo systemctl start svrjs or sudo /etc/init.d/svrjs start.

If you're using a ufw firewall, you can use:

sudo ufw allow https

You have now added encryption to the HTTP server!