How to Set Up a Remote Git Repository Server

This guide walks you through setting up a complete remote Git repository server on your local network, including SSH key authentication, proper main branch configuration, and creating your first repository.

Overview

You’ll learn to:

  1. Set up a remote Git Repository on a Linux server.

  2. Configure passwordless SSH key authentication.

  3. Create bare repositories with main as the default branch.

  4. Configure local Git client with optimal settings.

  5. Initialize and push your first project.


Architecture

Git Server

This diagram illustrates the two primary methods for a Client to interact with the Git Server.

The first path involves the Git Client communicating with the Git Server using the Git protocol, which is used for operations like pushing changes from the client to the server and pulling changes from the server to the client.

The second path shows the SSH Client on the client machine establishing a secure SSH connection with the SSH Server. This connection is used to securely tunnel to the Git Server for remote shell or to send commands.


Setup the Git Server and Client

Git Server Setup

In this document, we will use the hostname devel. If your Git Server’s hostname is different, replace it accordingly.

On the Git Server

Update the system:

sudo apt update && sudo apt upgrade -y

Install Git and SSH:

sudo apt install git openssh-server -y

Enable and start the ssh server:

sudo systemctl enable --now ssh

Create git user: You will be prompted to enter a password. This is the password used by su and ssh.

sudo adduser git

Switch to the new git user:

su - git

Configure Git identity:

git config --global user.name "Git Repository Server"
git config --global user.email "git@devel"

Set modern defaults:

git config --global init.defaultBranch main
git config --global pull.rebase true
git config --global push.default current

Verify configuration:

git config --global --list

Git Client Setup

On the Client

Copy your public key to the git server:

ssh-copy-id git@devel

Test the connection:

ssh git@devel
# Should log in without password prompt

Configure your identity (if not already done):

git config --global user.name "Your Name"
git config --global user.email "[email protected]"

Set modern defaults:

git config --global init.defaultBranch main
git config --global pull.rebase true
git config --global push.default current
git config --global push.autoSetupRemote true

Optional optimizations:

git config --global fetch.prune true
git config --global color.ui auto
git config --global core.editor nano  # nano or vim (or 'code --wait' for VS Code)

Verify configuration:

git config --global --list

Testing the Git Server Setup

On the Client

Create a projects folder or navigate to one if you already have it:

mkdir ~/workbooks

Create project test directory and change to it:

mkdir -p ~/workbooks/test
cd ~/workbooks/test

Initialize the local repository:

git init

Output:

Initialized empty Git repository in /home/user/workbooks/test/.git/

Verify HEAD is main on local repository:

git symbolic-ref HEAD

Output:

refs/heads/main

Create initial files:

echo "# My Test Project" > README.md
echo "# Ignore and Don't PUSH" > .gitignore

Add and commit:

git add .
git commit -m "Initial commit: Add README and .gitignore"

Output:

[main (root-commit) 7049a8e] Initial commit: Add README and .gitignore
 2 files changed, 2 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 README.md

Initialize the remote bare repository:

This is what actually creates the remote Git repository on the Git Server.

ssh git@devel 'git init --bare test.git'

Output:

Initialized empty Git repository in /home/git/test.git/

Verify HEAD is main on remote repository:

ssh git@devel 'git symbolic-ref HEAD'

Output:

refs/heads/main

Add remote for the local repository:

git remote add origin git@devel:test.git

Show what remote is set:

git remote -v

Output:

origin  git@devel:test.git (fetch)
origin  git@devel:test.git (push)

Push to remote:

git push

Your local main branch and remote branch (origin/main) should show the same commit, meaning they are in sync.

git branch -va

Output:

* main                7049a8e Initial commit: Add README and .gitignore
  remotes/origin/main 7049a8e Initial commit: Add README and .gitignore

Show the status:

git status

Output:

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

Setup Your First Project on the Git Server

Method 1: You have an Existing Local Project

Navigate to your project directory:

cd /path/to/your/project

Verify no git repository exists:

git status

Output:

fatal: not a git repository (or any of the parent directories): .git

If you have a local Git repository, git status returns its status. In that case, you can skip the next step.

Initialize git:

git init

Add remote:

Add a remote repository to your local repository.

Replace my-project.git with the name you want for your git project.

git remote add origin git@devel:my-project.git

Verify what remote is set:

git remote -v

Output:

origin  git@devel:my-project.git (fetch)
origin  git@devel:my-project.git (push)

Create the remote repository:

ssh git@devel 'git init --bare my-project.git'
# Output:
# Initialized empty Git repository in /home/git/my-project.git/
#
# Verify HEAD is main
ssh git@devel 'cd /home/git/my-project.git && git symbolic-ref HEAD'
# Output:
# refs/heads/main

In VS Code (or your IDE) open folder and navigate to your project folder. You should see source control enabled on the Git Server.

The official Microsoft documentation for VS Code Source Control is on the Visual Studio Code website.
Using Git source control in VS Code

Method 2: Start with a New Project

Create project directory, or navigate to existing:

mkdir -p ~/workbooks/my-new-project
cd ~/workbooks/my-new-project

Initialize with main branch:

git init

Add remote:

Add a remote repository to your local repository.

git remote add origin git@devel:my-new-project.git

Verify what remote is set:

git remote -v

Output:

origin  git@devel:my-new-project.git (fetch)
origin  git@devel:my-new-project.git (push)

Create the remote repository:

ssh git@devel 'git init --bare my-new-project.git'

Output:

Initialized empty Git repository in /home/git/my-project.git/

Verify HEAD is main:

ssh git@devel 'cd /home/git/my-project.git && git symbolic-ref HEAD'

Output:

refs/heads/main

In VS Code (or your IDE) open folder and navigate to your project folder. You should see source control enabled on the Git Server.

Method 3: Clone an Existing remote Repository

Clone remote repository test.git in this case - command line:

cd ~/workbooks # change to where you keep your projects.
git clone git@devel:test.git
cd test
git remote -v

Output:

origin  git@devel:test.git (fetch)
origin  git@devel:test.git (push)

Open project with your favorite IDE:

For VS Code; from the Welcome window, select Clone Git Repository. Enter the remote repository git@devel:test.git.


Troubleshooting

Common Issues and Solutions

SSH Connection Problems

# Debug SSH connection
ssh -v git@devel

# Check SSH service on remote git server
sudo systemctl status ssh

Permission Problems

# Fix repository permissions
sudo chown -R git:git /home/git/repositories
sudo chmod -R 755 /home/git/repositories

# Fix SSH permissions
sudo chmod 700 /home/git/.ssh
sudo chmod 600 /home/git/.ssh/authorized_keys

Default Branch Issues

# Check default branch on server
cd /home/git/test.git
git symbolic-ref HEAD

# Fix if showing master instead of main
git symbolic-ref HEAD refs/heads/main

Clone/Push Problems

# Test Git protocol
git ls-remote git@devel:test.git

# Verify remote URL
git remote -v

# If doesn't match the git ls-remote
# First remove
git remote remove origin
# Then
# Update remote if needed
git remote add origin git@devel:test.git

Repository Cleanup and Removal

During testing and development, you’ll create repositories that need to be cleaned up. Here’s how to safely remove them from your Git server.

Safe Repository Removal Process

Step 1: Assess What to Remove

On the Git Server

Log on to server (as git user):

ssh git@devel

List all repositories first:

ls -l /home/git/

Step 2: Remove Repository Safely

Method 1: Simple Removal (Recommended for Test Repos):

Remove the repository directory(s); do this for each of the directories you used for testing.

rm -rf ~/test.git

Verify removal:

ls -la /home/git/

Method 2: Safe Removal with Backup:

Create backup before removal:

tar -czf test.git.backups.tar.gz *.git

Move backup to safe location:

mkdir -p /home/git/backups/removed-repos
# move the backup to:
mv test.git.backup.tar.gz /home/git/backups/removed-repos/
# verify it's there
ls -l /home/git/backups/removed-repos/

Now remove the repository(s):

# Delete each of the directories you created during test and no longer need.
# For example deleting test.git
rm -rf test.git

Verify:

ls -la /home/git/

Local Cleanup (Client Side)

Don’t forget to clean up local test repositories:

On the Client

Remove local test repositories.

List current local Git repositories:

ls -l ~/workbook # path to where you created the test repositories

Remove specific local test repository:

rm -rf ~/path/to/test
rm -rf ~/path/to/my-project
rm -rf ~/path/to/my-new-project

Recovery (If Needed)

If you need to restore a removed repository on the Git Server:

On the Git Server

List available backups:

ls -la /home/git/backups/removed-repos/

List repositories in the backup:

 tar tfz  /home/git/backups/removed-repos/test.git.backups.tar.gz | grep -E '^[^/]+/?$' | sed 's|/$||'

Output:

test.git
my-project.git
my-new-project.git

Restore from backup:

To restore test.git for example

cd /home/git/
tar -xzf /home/git/backups/removed-repos/test.git.backups.tar.gz test.git

Verify restoration:

ls -la test.git/
cd test.git
git --no-pager log --oneline --all

Repository Safety and Self-Contained Nature

The repositories created using this setup guide are completely self-contained, which means:

# Each repository directory contains ALL data:
/home/git/project.git/
├── HEAD                    # Current branch pointer
├── config                  # Repository-specific config
├── description            # Repository description
├── hooks/                 # Git hooks (empty by default)
├── info/                  # Repository info
├── objects/               # All commits, files, history
└── refs/                  # All branches and tags

What This Means for Cleanup

Complete Safety: You can safely:

  • Delete the entire repository directory

  • Move the repository to another location

  • Backup just the repository directory

  • Copy the repository to another server

Safe Repository Operations

Because our repositories are self-contained, these operations are completely safe:

Safe Deletion

# This removes EVERYTHING related to the repository
rm -rf /home/git/test.git

Safe Backup

# This backs up EVERYTHING
tar -czf backup.tar.gz /home/git/test.git

Safe Move/Rename

# Move repository to new location
mv /home/git/old-name.git /home/git/new-name.git

Safe Copy

# Copy repository to another server
scp -r /home/git/project.git user@other-server:/git/repos/

This makes repository management much simpler. The repository directory truly contains everything you need for recovery!


Backup Git Repositories

Bash Backup Script (git_backup.sh)

This script will create a backup of all the repositories and keep the last 30 days by default.

  • No arguments: Runs the backup and cleanup.

  • -h: Displays the usage information.

  • Date string (e.g., 10-23-2025): Runs the top-level file listing for that specific date’s backup.

#!/bin/bash

# --- Configuration ---
# Directory containing the .git repositories to backup
SOURCE_DIR="/home/git"
# Directory where the backup archives will be stored
BACKUP_DIR="/home/git/backup"
# Number of days to retain backups (Change this variable to adjust retention)
RETENTION_DAYS=30
# --- End Configuration ---

# --- Usage/Help Function ---
usage() {
    echo "Usage: $0 [DATE | -h]"
    echo ""
    echo "This script performs nightly Git repository backups, cleans up old archives,"
    echo "or lists the contents of a specific date's backup file."
    echo ""
    echo "  No arguments: Perform backup and cleanup (runs via crontab)."
    echo "  DATE:         List the top-level contents of the backup file for the specified date."
    echo "                The date must be in the format MM-DD-YYYY (e.g., 10-23-2025)."
    echo "  -h:           Display this help message."
    echo ""
    exit 0
}

# --- Argument Parsing ---
if [ "$1" == "-h" ]; then
    usage
fi

# Change to the source directory to use the relative wildcard
if [ ! -d "$SOURCE_DIR" ]; then
    mkdir -p $SOURCE_DIR
fi
cd "$SOURCE_DIR" || exit 1

# --- Function to Perform Backup and Cleanup (No argument provided) ---
if [ -z "$1" ]; then
    CURRENT_DATE=$(date +"%m-%d-%Y")
    BACKUP_FILENAME="git.backups.${CURRENT_DATE}.tar.gz"
    BACKUP_PATH="${BACKUP_DIR}/${BACKUP_FILENAME}"

    echo "--- Starting Git Repository Backup ---"

    # 1. Perform Backup
    tar -czf "$BACKUP_PATH" *.git
    
    if [ $? -eq 0 ]; then
        echo "Successfully created backup: $BACKUP_PATH"
    else
        echo "Error: tar backup failed." >&2
        exit 1
    fi

    # 2. Clean up Old Backups
    echo "--- Cleaning up old backups (older than $RETENTION_DAYS days) ---"
    
    find "$BACKUP_DIR" -maxdepth 1 -name "git.backups.*.tar.gz" -type f -mtime +"$RETENTION_DAYS" -delete
    
    echo "Cleanup complete."
    echo "--- Backup Script Finished ---"

# --- Function to List Top-Level Files (Date argument provided) ---
elif [[ "$1" =~ ^[0-9]{2}-[0-9]{2}-[0-9]{4}$ ]]; then
    DATE_ARG="$1"
    LIST_FILENAME="git.backups.${DATE_ARG}.tar.gz"
    LIST_PATH="${BACKUP_DIR}/${LIST_FILENAME}"

    echo "--- Listing repositories in backup file: $LIST_PATH ---"

    if [ ! -f "$LIST_PATH" ]; then
        echo "Error: Backup file $LIST_PATH not found. Ensure the date format is MM-DD-YYYY." >&2
        
        # --- NEW DATE RANGE LOGIC ---
        # 1. Find all backup files and extract just the MM-DD-YYYY date part.
        AVAILABLE_DATES=$(find "$BACKUP_DIR" -maxdepth 1 -name "git.backups.*.tar.gz" -type f | \
                          grep -oE '[0-9]{2}-[0-9]{2}-[0-9]{4}' | \
                          sort -t'-' -k3,3n -k1,1n -k2,2n)
        
        if [ -n "$AVAILABLE_DATES" ]; then
            # The first line is the oldest date (min)
            MIN_DATE=$(echo "$AVAILABLE_DATES" | head -n 1)
            # The last line is the newest date (max)
            MAX_DATE=$(echo "$AVAILABLE_DATES" | tail -n 1)
            
            echo "Available backup dates are in the range of ${MIN_DATE} to ${MAX_DATE}."
        else
            echo "No backup files found in $BACKUPgit_backup.sh_DIR."
        fi
        # --- END NEW DATE RANGE LOGIC ---
        
        exit 1
    fi

    # Run the top-level listing command
    tar tfz "$LIST_PATH" | grep -E '^[^/]+/?$' | sed 's|/$||'
    
    if [ $? -ne 0 ]; then
        echo "Error: tar listing failed. The file may be corrupt or empty." >&2
    fi

Using the (git_backup.sh)

  1. Make sure the script is executable: chmod +x git_backup.sh

  2. Run the script with the help argument:

/path/to/git_backup.sh -h

Output will be:

Usage: /path/to/git_backup.sh [DATE | -h]

This script performs nightly Git repository backups, cleans up old archives,
or lists the contents of a specific date's backup file.

  No arguments: Perform backup and cleanup (runs via crontab).
  DATE:         List the top-level contents of the backup file for the specified date.
                The date must be in the format MM-DD-YYYY (e.g., 10-23-2025).
  -h:           Display this help message.

This complete setup gives you a professional Git development server with modern defaults, secure SSH access, and proper main branch configuration!