Setting up a personal Git server

Running a personal Git server is something that has been on my mind for quite a long time. One of the most popular solutions I have seen around is Gitea. A couple of months ago, when trying out different things, I decided to run Gitea locally to see how easy it would be to implement on my server. It turns out pretty easy, especially if you are using Docker. However, my server doesn't run Docker as of now and it also felt like customizing it would be hard (for example, getting rid of the username in the URLs). Gitea looks like a very good solution for self-hosting Git (and the sites look very nice!), but in my case, it felt like using a sledgehammer to crack a nut. I figured most self-hosted Git solutions would turn out to be a bit too much for my use case, so I decided to look into hosting Git without any other program.

I had experience setting up a bare-bones Git server only usable through SSH, so I looked up how to create a website with the bare repositories. It turns out there's even a built-in option1! Other programs have more or less similar looks, but I decided to check if there was any way to have a static generator for the webpage—the fewer things running on my server the better! And there is one (that I found): stagit. It is very simple, and it does the job. On top of that, the program is super small, which makes it very easy to modify it if needed2. I gave it a try and it worked nicely. I decided that it was a good solution for my use case, therefore having a vanilla Git server would work, so I started building it3.

Let's see how to set it up!

Setting up a Git server

What will happen is we'll have bare repositories on our server which we'll then clone/fetch/push to using SSH4. We'll put these repositories on the /srv/git directory—because I keep everything served from my server on /srv—but any location will work. To keep Git separated from the root user, we'll create a new git user, with /srv/git as its home folder, this way, the remote address will be git@domain:repo.git (no need for an absolute address). Let's do that:

useradd -d /srv/git git
mkdir /srv/git
chown git /srv/git

Now, let's create the folder for the SSH configuration where we'll add our public key.

su git
mkdir .ssh && chmod 700 .ssh
touch .ssh/authorized_keys && chmod 600 .ssh/authorized_keys

We could stop here, adding our key to the file (you can also use ssh-copy-id), but we can take a couple more steps to isolate the user from the server, useful especially if you want to share access to the git user with someone else.

To do that, we'll use the git-shell shell, which will only run scripts found on ~/git-shell-commands.5

chsh git -s $(which git-shell)

Now if you add someone else's public SSH key to the server, they won't be able to run any command. If you want to allow some commands, create scripts on ~/git-shell-commands. For example, I have scripts to initiate repositories, add SSH keys and other useful commands, you can see them all here.

Making repositories public

Now we can pull and push to our repository, and we can share access to them by adding SSH keys (or sharing the password if you use one). However, we might want to have public repositories that people should be able to clone without the need to have access to all of them. To do so, we'll use the Git Daemon (that uses the Git Protocol). All we have to do is run the following command (and keep it running, I recommend running a systemd service if you use systemd, there is an example here).

git daemon --reuseaddr --base-path=/srv/git/ /srv/git/

This daemon will only serve repositories that have a file named git-daemon-export-ok in them, so if you want to make a certain repository public, all you have to do is run:

touch /srv/git/repo.git/git-daemon-export-ok

Remove that file to make it private again. The cloning address will be git://domain/repo.git and you can't push to that address. You can also serve repositories through HTTP which will allow you to have fine-grained control over who can access which repositories, look it up if you are interested.

Making a website

Now we can host private and public repositories, but we may want to share the public ones easily. A website is a good way to allow people to quickly see your repositories without the need to clone every single one of them. We'll use stagit to create the HTML files which we'll host as a static site. First of all, let's install stagit:

git clone git://
cd stagit
sudo make install

To create a website for a repository run

stagit /path/to/repo.git

from the directory where you want the site built. To create an index file with all your repositories simply run

stagit-index /path/to/repo-1.git /path/to/repo-2.git > index.html

with the path to all your repositories. Make sure you have the correct information on the owner, description and url files so that stagit uses them when creating the HTML.

Having to do this is every time you update your repositories is not a reasonable solution, but we can set up a post-receive hook to do it for us. There are examples of scripts to use as an initial creation script for the whole site and a post-receive hook on stagit's source code repository. I have changed those scripts a bit to only process public repositories (the ones with the git-daemon-export-ok file) as well a modified the source a bit. You can find the changes on my stagit fork.

Pros of this setup

I have only been using this server for a couple of days, but I have also been setting up a bunch of suckless tools6, so I have been using Git a lot. One of the best things is that setting up repositories is very easy. No need to open a browser, log in to GitHub and go through the process of creating a new repository7. I just run

init reponame

Done! I can also set it up as public by running a script I have on my git-shell and change the description/owner/url just as easily.

Another thing I have noticed is that Git's clone/fetch/push commands are significantly faster on this server than GitLab or GitHub. However, I don't know compared to a self-hosted instance of Gitea or Gogs.

Cons of this setup

One thing that might be missed by this setup is the ability to download a tarball with the code from a certain commit, browse the code as it was in a given commit, see git blame, etc. This could be solved by using another tool for the website part—such as cgit—so if I ever want to do that, it shouldn't be hard.

Another thing is how the website looks. Other self-hosting solutions like Gogs, Gitea, GitLab... look a lot nicer than stagit. However this isn't a priority right now and I appreciate the ability to have full control over how the server works—and it has been very interesting to learn about it all. Once again this is something to do with the website, and not the repository hosting itself.

Final comments

It has been fun to set everything up and realize how easy it is to self-host Git without the need for other software. On top of that, I can now share my repositories from my domain, which means it is easier to stop using other hosting services if I stop wanting to accept their policies, similar to having email on my domain.

For now, I will still have my public repositories hosted on GitLab and GitHub, as I don't mind it and some people might be more used to those UIs. They also act as a backup in case something happens to my server or if I want to edit my repositories from a computer without SSH access to my server.

Finally, I have talked about some software that will allow you to self-host Git, but I don't want to end this post without mentioning sourcehut. I find it a very good solution because everyone can contribute to your projects without the need to create yet another account. Everything is done through email, which is nicely supported by Git (learn more here).

I almost forgot! If you want to check out my Git website or clone some repositories from my domain, you can find all of that here:

Edit: The post originally said that creating a new repository on GitLab was a long process. However, you can just push to a new remote address and GitLab will automatically create the new repository.

  1. Run git instaweb from a Git repository to try it. 

  2. I haven't modified the source code much yet, but I have some ideas in mind. 

  3. Funnily enough, I had already set up a Git server there for a couple of repositories containing a lot of personal information to avoid hosting them on GitLab or GitHub. I completely forgot about it. I deleted it and started from scratch, as I wanted to document the process on my personal wiki. 

  4. Bare repositories are simply a folder with the files of the .git directory of a repository. Indeed, you can clone a repository from your computer running git clone /path/to/repo/.git cloned-repo. This directory contains all the information of the repository, that is why Git is distributed. 

  5. If the chsh command doesn't work, make sure git-shell is in /etc/shells, if not, add it manually. 

  6. Fun fact: after setting everything up I realized that suckless uses stagit to show their repositories, indeed the author of stagit is the current maintainer of some suckless projects like st and dmenu

  7. I originally thought this was also the case for GitLab, however, you can push a new repository to a new remote address and GitLab will automatically create it.