Setting up a Git server from scratch
![](https://habrastorage.org/getpro/habr/upload_files/5ee/96c/be8/5ee96cbe8580309cbacc3ef075b639c3.png)
Any DevOps beginner begins their acquaintance with Git. This tool has become an integral part of the workflow of developers around the world. Many DevOps courses and tutorials cover setting up servers through popular platforms such as GitLab, and occasionally Gitea. However, I was interested in looking at another way – using a tool built into Git called GitWeb.
In this article, I will describe in detail the process of setting up a repository management system, using only the capabilities of Git itself. This approach may seem unusual, but it allows you to better understand the inner workings of Git and gain new skills that can be useful in your DevOps career.
In this guide, we will look at how to set up your own Git server based on Debian 12. The entire process of setting up a Git server is divided into the following tasks:
SSH setup;
creating users;
project initialization;
Git daemon configuration;
setting up a web interface for viewing repositories;
setting up additional security aspects.
Setting up SSH on the server
The first step is to install and configure an SSH server, which will provide secure access to our server.
Installing the openssh-server package
To install the packageopenssh-server
run the following commands:
apt update
apt install openssh-server
Setting up an SSH server
First you need to open the SSH configuration file:
nano /etc/ssh/sshd_config
It is recommended to change the following settings to improve security:
Port: Change the default port to another one to reduce the number of automated attacks;
PermitRootLogin: disable root login only after setting up a separate user to work with the server;
PasswordAuthentification: disable password login only after setting up keys;
PermitEmptyPasswords: disable empty passwords;
PubkeyAuthentification: Enable key authentication. Typically this option is enabled by default.
After making changes, restart the SSH service:
systemctl restart sshd
Creating an admin user
Why use a separate user with sudo?
There are a number of reasons for this:
User root bypasses all system restrictions, you can accidentally execute critical commands that can damage the system;
Account root will be primarily attacked by intruders;
sudo allows you to track user actions, all commands can be recorded in a log.
Naturally, there are many more reasons why it is worth using a separate user, but my article is not about that, so we will limit ourselves to these points.
Setting up the admin user
To manage the server, create a user admin with rights sudo:
adduser admin
apt install sudo
usermod -aG sudo admin
su admin
cd
mkdir .ssh
chmod 700 .ssh
touch .ssh/authorized_keys
chmod 600 .ssh/authorized_keys
Then install in the SSH configuration PermitRootLogin to a negative value and disable user access root:
sudo nano /etc/passwd
# Меняем root:x:0:0:root:/root:/bin/bash на root:x:0:0:root:/root:/sbin/nologin
If you try to go to rootthe following message will be displayed: This account is currently not available.
Creating a git user
To work with Git, let's create a separate user git:
adduser git
su git
cd
mkdir .ssh
chmod 700 .ssh
touch .ssh/authorized_keys
chmod 600 .ssh/authorized_keys
Restricting system access via git-shell
For now let the user git and does not have access to root rights, it still interacts with the system to one degree or another. This user is needed solely to work with repositories, but he still has access to system commands.
This behavior can be corrected using a special command shell – git-shell.
git-shell is a limited shell intended for Git users who should not have access to a full command line.
We can use this shell as follows:
Let's add git-shell to other shells:
nano /etc/shells
Let's add the following content:
/bin/git-shell /usr/bin/git-shell
Let's create the git-shell-commands directory:
mkdir /home/git/git-shell-commands
Now let's change the default command shell for the user git:
chsh -s $(command -v git-shell) git
Now if we log in to the user git via SSH, then we cannot do anything, since there are no registered commands there. If we need to create a repository, we will use the user adminand then change the access rights to the directory via chown.
However, if we need some commands from the git user, then we can add our own commands to the directory git-shell-commands.
What is the git-shell-commands directory for?
This directory is used to set up a restricted environment when Git is used via SSH in git-shell mode. If this directory does not exist, then we will not be able to use the user git interactively.
How to add a command to git-shell
In order to add a command for the git user, we must create a script inside the directory git-shell-commands. For example, let's create a script to list existing repositories:
Let's create a script:
touch /home/git/git-shell-commands/list-repos
Let's enter the following content:
#!/bin/bash ls /srv/git/
Let's make it executable:
chmod +x /home/git/git-shell-commands/list-repos
Now we can run the command list-repos and it will list all the repositories.
Setting up an SSH client to connect to the server
Generating keys on the client
Create the necessary SSH keys on the client:
ssh-keygen -t ed25519 -f ~/.ssh/key_for_git -C "Arthur Lokhov <arthurlokhov@gmail.com>"
ssh-keygen -t ed25519 -f ~/.ssh/key_for_admin -C "Arthur Lokhov <arthurlokhov@gmail.com>"
Next, you need to configure the SSH client to work with the newly created keys. To do this, run the command:
nano ~/.ssh/config
And write down the following contents, also don’t forget to change HostName And Port:
Host server_git
HostName <hostname>
StrictHostKeyChecking no
User git
Port <port>
ForwardAgent yes
IdentityFile /home/username/.ssh/key_for_git
IdentitiesOnly yes
UserKnownHostsFile=/dev/null
AddKeysToAgent yes
ServerAliveInterval 60
ServerAliveCountMax 1200
Host server_admin
HostName <hostname>
StrictHostKeyChecking no
User admin
Port <port>
ForwardAgent yes
IdentityFile /home/username/.ssh/key_for_admin
IdentitiesOnly yes
UserKnownHostsFile=/dev/null
AddKeysToAgent yes
ServerAliveInterval 60
ServerAliveCountMax 1200
The last step is to add public keys to the server. Keys are added to the corresponding users authorized_keys. If logging in via SSH is successful, then disable password login for SSH.
Creating a bare repository
After setting up users and SSH, the next step is to initialize the project on the server. In this section, we'll look at how to create a bare repository, make the first commit, and set up a remote repository for work.
But before moving on to the practical part, I think it’s necessary to dive a little into the theory.
What is a “naked” repository?
“Naked” repository(-bare) is used on the server to provide a central code repository. It does not contain a working copy of files and is intended solely for data exchange between developers, that is, a “bare” repository represents the contents of a directory .git.
How is the .git directory structured?
Inside the directory .git There are various files and directories that Git uses to store information about the repository, including change history, settings, links, and metadata.
This directory consists of the following components:
HEAD: This file contains a link to the current commit or branch you are working on;
branches/: This directory is used for legacy branch management mechanics that are actually no longer used in modern versions of Git. The normal directory is empty and does not play any role in managing branches;
config: a repository configuration file containing settings such as user information, remote repositories, command aliases, and other parameters specific to that repository;
description: a file commonly used in bare repositories to describe their purpose. In production repositories it has no special meaning;
hooks/: A directory for storing scripts that are run when certain Git actions occur, such as commits, merges, checkouts, and others. These scripts can be used to automate tasks and checks;
info/: Contains additional configuration files, such as the file
exclude
which is used to specify templates that are ignored only in this repository (the local equivalent of .gitignore);objects/: The directory where all data objects such as commits, trees, blobs (binaries) and tags are stored. These objects are identified by their SHA-1 hashes and are the basis for storing the history and contents of the repository;
refs/: contains links to commits for branches(heads/), tags(tags/) and remote branches(remotes/). These links help Git keep track of where branches and tags are located;
logs/: Contains a history of refs changes. For example, file logs/HEAD stores a history of changes pointing to referenced commits HEADwhich allows you to track branch changes.
Interacting Git with the .git directory
We have figured out what this directory consists of. But we haven’t figured out how Git interacts with this data. There are several Git interactions with files in this directory:
Commit: When you commit, Git takes files from the index, creates new objects in objects/and updates the links in refs/heads/ for the current branch;
Checkout: When you switch between branches, Git reads references from refs/heads/ and updates the working directory to the state corresponding to the commit pointed to by the link;
Push and pull: When communicating with remote repositories, Git updates the links in refs/remotes/ and moves objects to objects/;
Configuration management (config): Git reads the config file to apply settings such as user options and remote repositories.
Initializing the Project
IMPORTANT: All commands on the server from now on are executed through the user admin
Let's install Git if it is not already installed:
sudo apt install git
Let's create a directory for storing projects and configure access rights:
sudo mkdir -p /srv/git
sudo chown git:git /srv/git/
Let's create a bare repository:
cd /srv/git
sudo mkdir my_project.git
cd my_project.git
sudo git init --bare
cd ..
sudo chown git:git /srv/git/my_project.git/
Setting up a local repository and making your first commit
Now let's set up a local repository, make the first commit and send it to the server. To do this, run these commands:
mkdir my_project
cd my_project
git init
echo "# My Project" > README.md
git add README.md
git commit -m "Initial commit"
git remote add origin git@hostname:/srv/git/my_project.git
git push -u origin main
Now in file /srv/git/my_project/refs/heads/main a pointer will be stored HEAD(pointer to the last commit made in the current branch) to the current commit. It should be the same as your commit pointer in the local repository. This will be proof that the process was successful.
Setting up the Git Daemon
The Git daemon allows you to provide access to repositories through the protocol git://which allows for quick and easy setup for anonymous reading.
About the git daemon command
This command is used to control the Git daemon, as the name suggests. With the help of this command we will interact with our demon. With this command we will use the following flags:
reuseaddr means that the server will restart without waiting for existing connections to time out;
export-all means that all projects from the repository will be exported, otherwise a file must be created for each project git-daemon-export-ok;
base-path means that this path will be automatically substituted. That is, instead of git://hostname/srv/git/my_project.git can be used git://hostname/my_project.git.
Creating a service for systemd.
Because systemd is the most common initialization system, then I will give examples for it. I myself use Devuan With OpenRCbut I don’t see any point in providing settings for another initialization system.
Now let's move on to the process itself.
Let's create a service file for the Git daemon:
sudo nano /etc/systemd/system/git-daemon.service
Let's write down the following contents:
[Unit]
Description=Git daemon
[Service]
ExecStart=/usr/bin/git daemon --reuseaddr --export-all --base-path=/srv/git/ /srv/git/
Restart=always
RestartSec=500ms
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=git-daemon
User=git
Group=git
[Install]
WantedBy=multi-user.target
Let's start and activate the service.
sudo systemctl enable git-daemon
sudo systemctl start git-daemon
sudo systemctl status git-daemon
Setting up GitWeb
GitWeb provides a web interface for browsing Git repositories. We will look at two configuration methods:
Using git instaweb
git instaweb allows you to quickly deploy a web interface separately for each repository.
Let's install lighttpdif it is not already installed:
sudo apt install lighttpd
Let's go to the project directory and run git instaweb:
cd /srv/git/my_project.git
git instaweb
Let's go to the browser and enter the address http://hostname:1234to see the GitWeb interface.
![GitWeb launched via the git instaweb command gitweb with libhttpd](https://habrastorage.org/getpro/habr/upload_files/85e/b36/0d1/85eb360d1f2300357c624ce71e7b6855.png)
Let's shut down the server:
git instaweb --stop
Setting up GitWeb via Nginx
Let's install the necessary packages:
sudo apt install nginx gitweb fcgiwrap
Let's set it up Nginx to work with GitWeb. Let's create a configuration file for the site to work:
sudo nano /etc/nginx/sites-available/gitweb
Let's write down the following contents:
server {
listen 80;
server_name example.com;
location /index.cgi {
root /usr/share/gitweb/;
include fastcgi_params;
gzip off;
fastcgi_param SCRIPT_NAME $uri;
fastcgi_param GITWEB_CONFIG /etc/gitweb.conf;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
}
location / {
root /usr/share/gitweb/;
index index.cgi;
}
}
Let's activate the configuration:
sudo ln -s /etc/nginx/sites-available/gitweb /etc/nginx/sites-enabled/gitweb
sudo rm /etc/nginx/sites-enabled/default
sudo systemctl restart nginx
Let's go to the browser and enter the address of our server to see the GitWeb interface:
![GitWeb running via Nginx nginx with gitweb](https://habrastorage.org/getpro/habr/upload_files/758/d76/d2f/758d76d2ffccd87335c34b54fe972112.png)
Let's update the GitWeb configuration to display our repositories correctly:
sudo nano /etc/gitweb.conf
Let's change the path to the repositories:
$projectroot = "/srv/git";
Now our Git server is configured and we have access to the repositories via SSH, the Git daemon and the GitWeb web interface.
This configuration provides both security and usability, as well as support for anonymous and authenticated access to repositories.
![GitWeb, after configuration change nginx gitweb final](https://habrastorage.org/getpro/habr/upload_files/45f/16a/855/45f16a855df8a0b3168ae9e44a9ff54b.png)
Additional security and optimization considerations
Protection against brute-force attacks
To further protect our server, you can install and configure tools such as fail2banwhich will block IP (Internet Protocol) addresses that have made too many unsuccessful login attempts.
Let's install the package fail2ban:
sudo apt install fail2ban
Let's set it up fail2ban to secure SSH:
sudo nano /etc/fail2ban/jail.local
Let's add the following lines:
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 5
Let's create the file needed for logs, if it is not created, and restart fail2ban:
sudo touch /var/log/auth.log
sudo systemctl restart fail2ban
sudo systemctl status fail2ban
Setting up a firewall
To limit access to our server, we will configure a firewall, for example, ufw:
sudo apt install ufw
sudo ufw allow 443/tcp
sudo ufw allow 80/tcp
sudo ufw allow 20/tcp
# Также не забудьте открыть порт, через который у вас работает SSH
With the commands above we installed ufw and allowed requests to ports 443, 20 and 80.
Now let's turn it on ufw:
sudo ufw enable
You can try closing port 80 and opening GitWeb in a browser, it won't open.
Automatic package updates
To ensure system security, it is recommended to configure automatic package updates.
Let's install the package unattended-upgrades and configure it:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Now we will automatically download security updates.
Creating Backups
To prevent data loss, it is important to set up regular backups. We can use rsync or specialized tools for creating backup copies of repositories and server configurations.
Let's install rsync:
sudo apt install rsync
Let's set it up cron to automate backup:
crontab -e
Let's add the following task so that it runs every day at 2 am:
0 2 * * * rsync -avz /srv/git/ /srv/git-backup/
crontab syntax
The parameters go in the following order:
Minute
Hour
Day of the month
Month
Day of the week
Team
Bottom line
In this guide, we looked at how to set up your own Git server based on Debian 12, while ensuring a high level of security and functionality. We went through installing and configuring SSH, creating users, initializing projects, setting up the web interface, and covered additional security aspects such as protecting against brute-force attacks, setting up a firewall, and automatic package updates. We have also set up data backup.
This server can serve as a reliable support for managing your projects and software development.
What are you planning to improve?
At the moment I have plans to change or add the following things:
Backup not to another directory, but to another server;
Monitoring via Prometheus and Grafana;
Configured CI/CD pipeline for working with our repositories.
I'm currently thinking about how best to implement the above things and what else could (or even should) be added. Most likely, I will either make another article devoted to a more in-depth setup of working with the repository, or I will expand this one.
PS
Hello everyone, my name is Arthur Lokhov and this is my first article on Habré. What prompted me to choose this topic was that many courses discuss setting up work with repositories exclusively through GitLab, with rare exceptions in the form of Gitea. I believe that any DevOps should know how Git works from the inside and how to configure it from scratch in order to understand what is hidden under the same Gitlab and how it works.
It’s one thing to be able to work with tools, and another thing is to be able to work with tools and know how it works from the inside.
If you find any error, please let me know so that I can correct it, as I may have forgotten to include something important. Advice on how to improve your Git server is also welcome. The ideas for improvement given above will most likely be implemented by me when I move on to these topics (CI/CD and monitoring), and will be presented in the form of a series of articles that will be a logical continuation.