Self-Hosting a Personal Git Server

· 2349 words · 12 minutes

My Approach to Self-Hosting Git

I have often tried to self-host my Git repositories, but have always fallen short when I tried to find a suitable web interface to show on the front-end.

After a few years, I have finally found a combination of methods that allow me to easily self-host my projects, view them on the web, and access them from anywhere.

Before I dive into the details, I want to state a high-level summary of my self-hosted Git approach:


For the purposes of this walkthrough, I am assuming you have a URL ( or IP address ( addressed to the server that you will be using to host your git repositories.

Adding a Git User

In order to use the SSH method associated with git, we will need to add a user named git. If you have used the SSH method for other git hosting sites, you are probably used to the following syntax:

git clone [user@]server:project.git

The syntax above is an scp-like syntax for using SSH on the git user on the server to access your repository.

Let's delete any remnants of an old git user, if any, and create the new user account:

sudo deluser --remove-home git
sudo adduser git

Import Your SSH Keys to the Git User

Once the git user is created, you will need to copy your public SSH key on your local development machine to the git user on the server.

If you don't have an SSH key yet, create one with this command:


Once you create the key pair, the public should be saved to ~/.ssh/

If your server still has password-based authentication available, you can copy it over to your user's home directory like this:

ssh-copy-id git@server

Otherwise, copy it over to any user that you can access.

scp ~/.ssh/ your_user@your_server:

Once on the server, you will need to copy the contents into the git user's authorized_keys file:

cat > /home/git/.ssh/authorized_keys

(Optional) Disable Password-Based SSH

If you want to lock-down your server and ensure that no one can authenticate in via SSH with a password, you will need to edit your SSH configuration.

sudo nano /etc/ssh/sshd_config

Within this file, find the following settings and set them to the values I am showing below:

PermitRootLogin no
PasswordAuthentication no
AuthenticationMethods publickey

You may have other Authentication Methods required in your personal set-up, so the key here is just to ensure that AuthenticationMethods does not allow passwords.

Setting up the Base Directory

Now that we have set-up a git user to handle all of transport methods, we need to set-up the directory that we will be using as our base of all repositories.

In my case, I am using /git as my source folder. To create this folder and assign it to the user we created, execute the following commands:

sudo mkdir /git
sudo chown -R git:git /git

Creating a Test Repository

On your server, switch over to the git user in order to start managing git files.

su git

Once logged-in as the git user, go to your base directory and create a test repository.

cd /git
mkdir test.git && cd test.git
git init --bare

If you want to make this repo viewable/cloneable to the public via the git:// protocol, you need to create a git-daemon-export-ok file inside the repository.

touch git-daemon-export-ok

Opening the Firewall

Don't forget to open up ports on the device firewall and network firewall if you want to access these repositories publicly. If you're using default ports, forward ports 22 (ssh) and 8169 (git) from your router to your server's IP address.

If your server also has a firewall, ensure that the firewall allows the same ports that are forwarded from the router. For example, if you use ufw:

sudo ufw allow 22
sudo ufw allow 8169

Non-Standard SSH Ports

If you use a non-standard port for SSH, such as 9876, you will need to create an SSH configuration file on your local development machine in order to connect to your server's git repositories.

To do this, you'll need to define your custom port on your client machine in your ~/.ssh/config file:

nano ~/.ssh/config
  Port 9876
  User git

Testing SSH

There are two main syntaxes you can use to manage git over SSH:

I prefer the first, which is an scp-like syntax. To test it, try to clone the test repository you set up on the server:

git clone

Enabling Read-Only Access

If you want people to be able to clone any repository where you've placed a git-daemon-export-ok file, you will need to start the git daemon.

To do this on a system with systemd, create a service file:

sudo nano /etc/systemd/system/git-daemon.service

Inside the git-daemon.service file, paste the following:

Description=Start Git Daemon

ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/git/ /git/





Once created, enable and start the service:

sudo systemctl enable git-daemon.service
sudo systemctl start git-daemon.service

To clone read-only via the git:// protocol, you can use the following syntax:

git clone git://

Migrating Repositories

At this point, we have a working git server that works with both SSH and read-only access.

For each of the repositories I had hosted a different provider, I executed the following commands in order to place a copy on my server as my new source of truth:


su git
mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git
git init --bare

# If you want to make this repo viewable/cloneable to the public
touch git-daemon-export-ok


git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git
git push

Using cgit

If you want a web viewer for your repositories, you can use various tools, such as gitweb, cgit, or klaus. I chose cgit due to its simple interface and fairly easy set-up (compared to others). Not to mention that the Linux kernel uses cgit.


Installing via Docker is as simple as:

sudo docker run --name=cgit -d -p 8763:80 -v /git:/git invokr/cgit

Once it's finished installing, you can access the site at <SERVER_IP>:8763 or use a reverse-proxy service to forward cgit to a URL, such as See the next section for more details on reverse proxying a URL.

Nginx Reverse Proxy

I am using Nginx as my reverse proxy so that the cgit Docker container can use as its URL. To do so, I simply created the following configuration file:

sudo nano /etc/nginx/sites-available/
server {
        listen 80;

      	if ($host = {
		        return 301 https://$host$request_uri;

    	  return 404;

server {
        listen 443 ssl http2;

        location / {
                # The final `/` is important.
		            proxy_pass http://localhost:8763/;
                add_header X-Frame-Options SAMEORIGIN;
                add_header X-XSS-Protection "1; mode=block";
                proxy_redirect off;
                proxy_buffering off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-Port $server_port;

        include /etc/letsencrypt/options-ssl-nginx.conf;
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

Once created, symlink it and restart the server.

sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/
sudo systemctl restart nginx.service

As we can see below, my site at is available and running:

cgit example

Settings Up Git Details

Once you have cgit running, you can add some small details, such as repository owners and descriptions by editing the following files.

The description file within the repository on your server will display the description online.

cd /git/example.git
nano description

You can add a [gitweb] block to the config file in order to display the owner of the repository.

cd /git/example.git
nano config
	owner = "YourName"

Editing cgit

If you want to edit other items, such as the actual site name or description of cgit, you will need to open the Docker container and edit the cgitrc file inside:

sudo docker exec -it cgit bash
vi /etc/cgitrc

Below is an example configuration for cgitrc. You can find all of the configuration options within the configuration manual.

robots=noindex, nofollow



clone-url=git://$CGIT_REPO_URL ssh://$CGIT_REPO_URL


## List of common mimetypes

## Search for these files in the root of the default branch of repositories
## for coming up with the about page:

Fix: Syntax Highlighting & README Rendering

After completing my install and playing around with it for a few days, I noticed two issues:

  1. Syntax highlighting did not work when viewing the source code within a file.
  2. The about tab within a repository was not rendered to HTML.

The following process fixes these issues. First, we need to be on the server hosting cgit and then enter our Docker container:

sudo docker exec -it cgit bash

Within this container, we need to go to our document root for the cgit web files and create a filters directory:

cd /var/www/htdocs/cgit
mkdir filters
cd filters

In this directory, we are going to pull down a couple formatting files from the main cgit repository.

curl >
chmod 755
curl >
chmod 755

Once the main files are created and permissions are modified, we need create another directory called html-converters inside the filters directory. In this new directory, we will curl another file that will convert Markdown to HTML.

mkdir html-converters && cd html-converters
curl > md2html
chmod 755 md2html

If you need other filters or html-converters found within the cgit project files, repeat the curl and chmod process above for whichever files you need.

Lastly, we just need to add the following to our cgitrc file in order for cgit to know where our filtering files are:

# Highlight source code with python pygments-based highlighter

# Format markdown, restructuredtext, manpages, text files, and html files
# through the right converters

Now you should see that syntax highlighting and README rendering to the about tab is fixed. See the images below for illustrations:

Syntax Highlighting: Syntax highlighting

README Rendering: README Rendering


I won't go into much detail in this section, but you can fully theme your installation of cgit since you have access to the cgit.css file in your web root.

For example, I created a Stylus theme called dark-cgit to use for my personal website. This theme just needs the URL updated to work for any other cgit-based site. Here's what this theme looks like in action:

Index: dark-cgit home

Syntax Highlighting: dark-cgit code

README Rendering: dark-cgit readme


The last thing to note is that running services on your own equipment means that you're assuming a level of risk that exists regarding data loss, catastrophes, etc. In order to reduce the impact of any such occurrence, I suggest backing up your data regularly.

Backups can be automated via cron, by hooking your base directory up to a cloud provider, or even setting up hooks to push all repository info to git mirrors on other git hosts. Whatever the method, make sure that your data doesn't vanish in the event that your drives or servers fail.