--- title: How to run a Git server on GNU/Linux? tags: - git - vcs - development - server - web - webmaster - gitweb categories: Tips thumbnail: /images/covers/How-to-run-a-Git-server-on-GNU-Linux.png date: 2024-03-11 21:33:10 --- 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: ```sh 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: ```sh 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.](/2024/03/05/Mastering-the-Basics-of-Git-The-Ultimate-Guide-to-Git-Commands-for-Software-Developers/) Then create a new Git user (we're using `/var/lib/gituser` directory as a user directory): ```sh 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: ```sh 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](/images/git-server-initial-setup.png) 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: ```sh 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**: ```sh #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: ```sh sudo ufw allow git ``` ![The Git protocol setup of a Git server](/images/git-server-git-protocol.png) You can test the server by running this command on a client (assuming that the server has `10.0.0.2` address): ```sh 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](/images/git-server-git-protocol-test.png) 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: ```sh 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): ```sh 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): ```sh 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): ```sh ssh-keygen -o ssh-copy-id git@10.0.0.2 #Replace `10.0.0.2` with your server address. ``` ![Preparing the SSH keys...](/images/git-server-ssh-protocol-keyprep.png) Finally you can revert the shell change (run it on server): ```sh 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.](https://brnrd.eu/freebsd/2023-08-06/your-private-git-server-in-a-chroot.html) ![The SSH setup of a Git server](/images/git-server-ssh-protocol.png) You can test the server by running this command on a client (assuming that the server has `10.0.0.2` address): ```sh 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](/images/git-server-ssh-protocol-test.png) 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](https://svrjs.org) and [SVR.JS installer for GNU/Linux](https://downloads.svrjs.org/installer). You can use these commands to install SVR.JS: ```sh 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](/images/git-server-http-protocol-welcome.png) 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`): ```sh sudo usermod -aG svrjs git sudo usermod -aG git svrjs ``` If you're using a `ufw` firewall, you can use: ```sh sudo ufw allow http ``` For other web servers, you can read [the git-http-backend documentation](https://git-scm.com/docs/git-http-backend) and [the GitWeb documentation](https://git-scm.com/docs/gitweb). 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](/images/git-server-http-protocol-empty-dirlisting.png) 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](https://svrjs.org/mods) and run the commands: ```sh 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: ```sh 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: ```json { "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](https://svrjs.org/docs). After the configuration changes, you can add the `git` user to the HTTP server: ```sh sudo svrpasswd -a git ``` Later, modify the `/etc/gitweb.conf` file (GitWeb configuarion) like this: ```perl $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](https://git-scm.com/docs/gitweb.conf.html). 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](/images/git-server-http-protocol.png) You can test the server by running this command on a client (assuming that the server has `10.0.0.2` address): ```sh 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](/images/git-server-http-protocol-test.png) 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](/images/git-server-http-protocol-gitweb-test.png) 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`](https://certbot.eff.org/)). 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: ```json ... "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: ```sh sudo ufw allow https ``` You have now added encryption to the HTTP server!