Private Git Server Configuration: Complete Self-Hosted Git Solution Guide

In today's development landscape, version control is essential for managing code, collaborating with teams, and maintaining project history. While cloud-based solutions like GitHub, GitLab, and Bitbucket are popular, hosting your own private Git server offers complete control over your code, enhanced security, and freedom from third-party dependencies. This comprehensive guide walks you through setting up, configuring, and managing a private Git server on your own infrastructure.

Introduction

A private Git server provides centralized version control infrastructure under your complete control. Whether you're concerned about data sovereignty, need enhanced security for proprietary code, want to avoid subscription costs, or simply prefer self-hosted solutions, setting up your own Git server is a practical and achievable goal.

Why Host Your Own Git Server?

Security and Privacy: Your code never leaves your infrastructure, giving you complete control over access and data protection. This is crucial for organizations handling sensitive intellectual property or operating under strict compliance requirements.

Cost Efficiency: After initial setup, hosting costs are typically lower than enterprise cloud Git solutions, especially for larger teams or extensive repository collections.

Customization: Full control over the server environment allows custom integrations, backup strategies, and workflows tailored to your specific needs.

No Vendor Lock-in: Independence from third-party service providers means no concerns about service changes, price increases, or platform availability.

Learning Opportunity: Setting up a Git server deepens your understanding of Git's architecture and server administration.

Git Server Approaches

This guide covers multiple approaches to hosting Git, from basic SSH-based solutions to full-featured web interfaces:

  1. Basic SSH Git Server: Simple, secure, command-line focused
  2. Gitolite: Advanced access control with minimal overhead
  3. Gitea: Lightweight, self-contained Git service with web interface
  4. GitLab CE: Full-featured DevOps platform with CI/CD integration

Each approach has different resource requirements, feature sets, and complexity levels, allowing you to choose the solution that best fits your needs.

Prerequisites

Before beginning the installation, ensure you have:

System Requirements

Minimum Requirements (Basic SSH Git Server):

  • Linux server (Ubuntu 20.04+, Debian 11+, CentOS 8+, Rocky Linux 8+)
  • 1 CPU core
  • 512MB RAM
  • 10GB disk space
  • Root or sudo access

Recommended Requirements (Gitea):

  • 2 CPU cores
  • 2GB RAM
  • 20GB+ disk space (depending on repository size)
  • Domain name or static IP address

Recommended Requirements (GitLab CE):

  • 4 CPU cores
  • 4GB RAM (8GB recommended)
  • 50GB+ disk space
  • Domain name with SSL certificate

Required Software

  • Git (version 2.x or higher)
  • SSH server (OpenSSH)
  • Text editor (vim, nano, or preferred alternative)
  • Web server (Nginx or Apache) for web-based solutions
  • Database (PostgreSQL or MySQL) for Gitea/GitLab

Network Requirements

  • SSH port 22 (or custom port) open for Git operations
  • Port 80/443 for web interface (web-based solutions)
  • Static IP address or dynamic DNS for consistent access
  • Domain name (optional but recommended for web-based solutions)

Security Prerequisites

  • Firewall configured (UFW, firewalld, or iptables)
  • SSH key-based authentication configured
  • Regular backup solution planned
  • SSL/TLS certificates for HTTPS (Let's Encrypt recommended)

Installation

Method 1: Basic SSH Git Server

The simplest Git server requires only SSH access and Git installation. This method is ideal for small teams or personal use.

Step 1: Install Git

# Ubuntu/Debian
sudo apt update
sudo apt install -y git

# CentOS/Rocky Linux
sudo dnf install -y git

Verify installation:

git --version

Step 2: Create Git User

Create a dedicated user for Git operations:

sudo adduser git
sudo passwd git  # Set a secure password

Step 3: Configure SSH Access

Set up SSH key-based authentication:

# Switch to git user
sudo su - git

# Create SSH directory
mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Add client SSH public keys to ~/.ssh/authorized_keys:

# On client machine, generate SSH key if needed
ssh-keygen -t ed25519 -C "[email protected]"

# Copy public key to server
cat ~/.ssh/id_ed25519.pub | ssh git@your-server "cat >> ~/.ssh/authorized_keys"

Step 4: Create Repository Directory

# As git user
sudo su - git
mkdir -p ~/repositories
cd ~/repositories

Step 5: Initialize Bare Repository

# Create bare repository
git init --bare project.git
cd project.git
git config core.sharedRepository group

Step 6: Configure Git Server

Set appropriate permissions:

# As root
sudo chown -R git:git /home/git/repositories
sudo chmod -R 755 /home/git/repositories

Step 7: Test Remote Access

From a client machine:

# Clone the repository
git clone git@your-server:repositories/project.git

# Make changes and push
cd project
echo "# My Project" > README.md
git add README.md
git commit -m "Initial commit"
git push origin master

Method 2: Gitolite Installation

Gitolite provides advanced access control while maintaining simplicity.

Step 1: Install Gitolite

# Ubuntu/Debian
sudo apt update
sudo apt install -y gitolite3

# CentOS/Rocky Linux
sudo dnf install -y gitolite3

Step 2: Configure Gitolite

# Create git user
sudo adduser git

# Copy admin SSH key
sudo cp /path/to/admin-key.pub /tmp/admin.pub
sudo chmod 644 /tmp/admin.pub

# Initialize gitolite
sudo su - git
gitolite setup -pk /tmp/admin.pub

Step 3: Clone Admin Repository

From your local machine:

git clone git@your-server:gitolite-admin
cd gitolite-admin

Step 4: Configure Access Control

Edit conf/gitolite.conf:

# gitolite.conf
@developers = alice bob charlie
@admins = admin

repo gitolite-admin
    RW+ = @admins

repo project1
    RW+ = @admins
    RW = @developers

repo project2
    RW+ = @admins
    R = @developers

Commit and push changes:

git add conf/gitolite.conf
git commit -m "Configure repository access"
git push origin master

Method 3: Gitea Installation

Gitea provides a lightweight, self-hosted Git service with web interface.

Step 1: Install Dependencies

# Ubuntu/Debian
sudo apt update
sudo apt install -y git wget sqlite3

# CentOS/Rocky Linux
sudo dnf install -y git wget sqlite

Step 2: Create Gitea User

sudo adduser --system --shell /bin/bash --group --disabled-password --home /home/git git

Step 3: Download Gitea

# Check latest version at https://github.com/go-gitea/gitea/releases
wget -O /tmp/gitea https://dl.gitea.io/gitea/1.21.0/gitea-1.21.0-linux-amd64
sudo mv /tmp/gitea /usr/local/bin/gitea
sudo chmod +x /usr/local/bin/gitea

Step 4: Create Directory Structure

sudo mkdir -p /var/lib/gitea/{custom,data,log}
sudo chown -R git:git /var/lib/gitea
sudo chmod -R 750 /var/lib/gitea

sudo mkdir /etc/gitea
sudo chown root:git /etc/gitea
sudo chmod 770 /etc/gitea

Step 5: Create Systemd Service

Create /etc/systemd/system/gitea.service:

[Unit]
Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target

[Service]
RestartSec=2s
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/gitea/
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea

[Install]
WantedBy=multi-user.target

Step 6: Start Gitea

sudo systemctl daemon-reload
sudo systemctl enable gitea
sudo systemctl start gitea
sudo systemctl status gitea

Step 7: Configure Firewall

# UFW (Ubuntu/Debian)
sudo ufw allow 3000/tcp

# firewalld (CentOS/Rocky)
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --reload

Step 8: Complete Web Installation

Access http://your-server:3000 and complete the installation wizard.

Method 4: GitLab CE Installation

GitLab provides a complete DevOps platform with Git hosting, CI/CD, and project management.

Step 1: Install Dependencies

# Ubuntu/Debian
sudo apt update
sudo apt install -y curl openssh-server ca-certificates tzdata perl

# CentOS/Rocky Linux
sudo dnf install -y curl policycoreutils openssh-server perl
sudo systemctl enable sshd
sudo systemctl start sshd

Step 2: Add GitLab Repository

# Ubuntu/Debian
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash

# CentOS/Rocky Linux
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash

Step 3: Install GitLab

# Ubuntu/Debian
sudo EXTERNAL_URL="https://gitlab.yourdomain.com" apt install -y gitlab-ce

# CentOS/Rocky Linux
sudo EXTERNAL_URL="https://gitlab.yourdomain.com" dnf install -y gitlab-ce

Step 4: Configure GitLab

Edit /etc/gitlab/gitlab.rb:

external_url 'https://gitlab.yourdomain.com'
gitlab_rails['gitlab_shell_ssh_port'] = 22
gitlab_rails['time_zone'] = 'UTC'

Reconfigure GitLab:

sudo gitlab-ctl reconfigure

Step 5: Access GitLab

Access https://gitlab.yourdomain.com and set the root password.

Configuration

SSH Server Configuration

Optimize SSH for Git operations by editing /etc/ssh/sshd_config:

# Security settings
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no

# Performance settings
ClientAliveInterval 120
ClientAliveCountMax 3
MaxStartups 10:30:100

# Git-specific
AllowUsers git

Restart SSH:

sudo systemctl restart sshd

Nginx Reverse Proxy for Gitea

Create /etc/nginx/sites-available/gitea:

server {
    listen 80;
    server_name git.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name git.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/git.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/git.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}

Enable configuration:

sudo ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Database Configuration for Gitea

For production use, configure PostgreSQL instead of SQLite.

Install PostgreSQL

# Ubuntu/Debian
sudo apt install -y postgresql postgresql-contrib

# CentOS/Rocky Linux
sudo dnf install -y postgresql-server postgresql-contrib
sudo postgresql-setup --initdb
sudo systemctl enable postgresql
sudo systemctl start postgresql

Create Database

sudo -u postgres psql

CREATE DATABASE gitea;
CREATE USER gitea WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE gitea TO gitea;
\q

Update Gitea Configuration

Edit /etc/gitea/app.ini:

[database]
DB_TYPE = postgres
HOST = 127.0.0.1:5432
NAME = gitea
USER = gitea
PASSWD = secure_password

Restart Gitea:

sudo systemctl restart gitea

Backup Configuration

Automated Backup Script

Create /usr/local/bin/git-backup.sh:

#!/bin/bash

BACKUP_DIR="/backup/git"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

# Create backup directory
mkdir -p "$BACKUP_DIR"

# Backup repositories (basic Git server)
tar -czf "$BACKUP_DIR/repositories_$DATE.tar.gz" /home/git/repositories

# Backup Gitea (if using)
if systemctl is-active --quiet gitea; then
    sudo -u git gitea dump -c /etc/gitea/app.ini -f "$BACKUP_DIR/gitea_$DATE.zip"
fi

# Remove old backups
find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -delete

echo "Backup completed: $DATE"

Make executable:

sudo chmod +x /usr/local/bin/git-backup.sh

Schedule with Cron

sudo crontab -e

# Add daily backup at 2 AM
0 2 * * * /usr/local/bin/git-backup.sh >> /var/log/git-backup.log 2>&1

Practical Examples

Example 1: Creating and Cloning Repository

Server-side:

# SSH Git server
sudo su - git
cd ~/repositories
git init --bare myproject.git

# Gitea/GitLab: Use web interface

Client-side:

# SSH server
git clone git@your-server:repositories/myproject.git

# Gitea
git clone https://git.yourdomain.com/username/myproject.git

# GitLab
git clone https://gitlab.yourdomain.com/username/myproject.git

Example 2: Managing Multiple Users (Gitolite)

# Clone admin repository
git clone git@your-server:gitolite-admin
cd gitolite-admin

# Add user SSH key
cp ~/Downloads/user-key.pub keydir/users/newuser.pub

# Grant repository access
cat >> conf/gitolite.conf << EOF

repo newproject
    RW+ = admin
    RW = newuser
EOF

# Apply changes
git add keydir/users/newuser.pub conf/gitolite.conf
git commit -m "Add newuser with access to newproject"
git push origin master

Example 3: Setting Up Git Hooks

Pre-receive hook for code quality checks:

Create repositories/project.git/hooks/pre-receive:

#!/bin/bash

while read oldrev newrev refname; do
    # Check commit message format
    if ! git log --format=%s $oldrev..$newrev | grep -qE '^(feat|fix|docs|style|refactor|test|chore):'; then
        echo "Error: Commit messages must follow conventional commits format"
        exit 1
    fi
done

exit 0

Make executable:

chmod +x repositories/project.git/hooks/pre-receive

Example 4: Mirroring External Repository

# Create mirror repository
cd /home/git/repositories
git clone --mirror https://github.com/username/project.git

# Set up automatic sync
cat > /usr/local/bin/sync-mirror.sh << 'EOF'
#!/bin/bash
cd /home/git/repositories/project.git
git fetch --all --prune
EOF

chmod +x /usr/local/bin/sync-mirror.sh

# Schedule sync every hour
echo "0 * * * * /usr/local/bin/sync-mirror.sh" | sudo -u git crontab -

Example 5: Repository Migration

From GitHub to private server:

# Clone with all branches and tags
git clone --mirror https://github.com/username/project.git

# Create new repository on private server
ssh git@your-server "cd repositories && git init --bare project.git"

# Push to private server
cd project.git
git push --mirror git@your-server:repositories/project.git

# Update local clone to use new remote
cd /path/to/local/clone
git remote set-url origin git@your-server:repositories/project.git

Verification

Testing SSH Connectivity

# Test SSH connection
ssh -T git@your-server

# Expected output for basic Git server:
# PTY allocation request failed on channel 0

# Expected output for Gitolite:
# hello username, this is git@server running gitolite3...

Repository Access Verification

# Clone test
git clone git@your-server:repositories/test.git test-clone

# Push test
cd test-clone
echo "test" > test.txt
git add test.txt
git commit -m "Test commit"
git push origin master

Service Status Checks

# Check Gitea
sudo systemctl status gitea
curl http://localhost:3000

# Check GitLab
sudo gitlab-ctl status
curl http://localhost

# Check SSH
sudo systemctl status sshd
ss -tlnp | grep :22

Performance Testing

# Clone performance test
time git clone git@your-server:repositories/large-repo.git

# Push performance test
cd large-repo
dd if=/dev/zero of=largefile bs=1M count=100
git add largefile
time git commit -m "Add large file"
time git push origin master

Troubleshooting

Issue 1: Permission Denied (publickey)

Symptom: Permission denied (publickey) when attempting to clone or push

Solution:

# Verify SSH key is added
ssh-add -l

# Add SSH key if missing
ssh-add ~/.ssh/id_ed25519

# Test SSH connection with verbose output
ssh -vT git@your-server

# Check authorized_keys on server
sudo cat /home/git/.ssh/authorized_keys

# Verify permissions
sudo ls -la /home/git/.ssh
# Should show: drwx------ for .ssh directory
# Should show: -rw------- for authorized_keys file

# Fix permissions if needed
sudo chmod 700 /home/git/.ssh
sudo chmod 600 /home/git/.ssh/authorized_keys
sudo chown -R git:git /home/git/.ssh

Issue 2: Repository Not Found

Symptom: fatal: repository not found error

Solution:

# Verify repository exists on server
sudo ls -la /home/git/repositories/

# Check repository path in clone command
# Correct: git clone git@server:repositories/project.git
# Wrong: git clone git@server:project.git

# Verify git user can access repository
sudo -u git ls -la /home/git/repositories/project.git

Issue 3: Push Rejected

Symptom: ! [remote rejected] master -> master (pre-receive hook declined)

Solution:

# Check hook permissions
sudo ls -la /home/git/repositories/project.git/hooks/

# Hooks must be executable
sudo chmod +x /home/git/repositories/project.git/hooks/pre-receive

# Check hook output for errors
sudo -u git /home/git/repositories/project.git/hooks/pre-receive

# Temporarily disable hook for testing
sudo mv /home/git/repositories/project.git/hooks/pre-receive \
         /home/git/repositories/project.git/hooks/pre-receive.disabled

Issue 4: Gitea Web Interface Not Accessible

Symptom: Cannot access Gitea web interface

Solution:

# Check Gitea service status
sudo systemctl status gitea

# Check if port is listening
sudo ss -tlnp | grep 3000

# Check Gitea logs
sudo journalctl -u gitea -n 50

# Verify firewall rules
sudo ufw status | grep 3000
# Or for firewalld:
sudo firewall-cmd --list-ports

# Test local access
curl http://localhost:3000

Issue 5: Slow Git Operations

Symptom: Clone and push operations are extremely slow

Solution:

# Enable Git repack
cd /home/git/repositories/project.git
sudo -u git git repack -ad

# Enable garbage collection
sudo -u git git gc --aggressive

# Check disk I/O
iostat -x 1 5

# Check available memory
free -h

# Enable Git compression
git config --global core.compression 9

# For large repositories, increase buffer size
git config --global http.postBuffer 524288000

Issue 6: Database Connection Failed (Gitea)

Symptom: Gitea fails to start with database connection errors

Solution:

# Check PostgreSQL status
sudo systemctl status postgresql

# Verify database exists
sudo -u postgres psql -l | grep gitea

# Test connection
sudo -u postgres psql -d gitea -c "SELECT version();"

# Check Gitea configuration
sudo cat /etc/gitea/app.ini | grep -A5 "\[database\]"

# Verify PostgreSQL allows local connections
sudo grep "local.*all.*all" /etc/postgresql/*/main/pg_hba.conf

# Restart services
sudo systemctl restart postgresql
sudo systemctl restart gitea

Best Practices

Security Best Practices

  1. Disable Password Authentication: Always use SSH keys
# /etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
  1. Implement Fail2ban: Protect against brute force attacks
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
  1. Regular Security Updates:
# Ubuntu/Debian
sudo apt update && sudo apt upgrade -y

# CentOS/Rocky Linux
sudo dnf update -y
  1. Use HTTPS for Web Interfaces: Always configure SSL/TLS
# Let's Encrypt
sudo certbot --nginx -d git.yourdomain.com
  1. Implement Access Control: Use Gitolite or GitLab roles for granular permissions

  2. Enable Two-Factor Authentication: For Gitea/GitLab web interfaces

  3. Restrict Git User Shell:

# Limit git user to Git operations only
sudo usermod -s /usr/bin/git-shell git

Backup Best Practices

  1. Automated Daily Backups: Schedule regular backups with cron

  2. Off-site Storage: Store backups in remote location

# Sync to remote server
rsync -avz /backup/git/ remote-server:/backup/git/
  1. Test Restore Procedures: Regularly verify backups are recoverable
# Test restore
mkdir /tmp/restore-test
tar -xzf /backup/git/repositories_20240101.tar.gz -C /tmp/restore-test
  1. Version Retention: Keep multiple backup versions (daily, weekly, monthly)

  2. Document Recovery Process: Maintain clear restoration documentation

Performance Best Practices

  1. Repository Maintenance: Regular garbage collection and repacking
# Create maintenance script
#!/bin/bash
for repo in /home/git/repositories/*.git; do
    cd "$repo"
    git gc --aggressive --prune=now
    git repack -ad
done
  1. Enable Caching: For web-based Git servers
# Nginx caching configuration
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=gitea_cache:10m max_size=1g;

location / {
    proxy_cache gitea_cache;
    proxy_cache_valid 200 10m;
    proxy_pass http://localhost:3000;
}
  1. Resource Monitoring: Monitor CPU, memory, and disk usage
# Install monitoring tools
sudo apt install htop iotop nethogs
  1. Optimize Database: For Gitea/GitLab with database backends
-- PostgreSQL optimization
VACUUM ANALYZE;
REINDEX DATABASE gitea;
  1. SSD Storage: Use SSDs for repository storage when possible

Collaboration Best Practices

  1. Standardized Workflows: Document Git workflows for team

  2. Branch Protection: Implement branch protection rules

  3. Code Review Process: Use merge/pull requests for all changes

  4. Commit Message Standards: Enforce conventional commit messages with hooks

  5. Documentation: Maintain comprehensive repository documentation

Operational Best Practices

  1. Monitoring and Alerting: Set up service monitoring
# Example monitoring script
#!/bin/bash
if ! systemctl is-active --quiet gitea; then
    echo "Gitea is down!" | mail -s "Alert: Gitea Down" [email protected]
fi
  1. Log Rotation: Configure proper log rotation
# /etc/logrotate.d/gitea
/var/log/gitea/*.log {
    daily
    rotate 14
    compress
    delaycompress
    notifempty
    create 640 git git
    sharedscripts
    postrotate
        systemctl reload gitea
    endscript
}
  1. Capacity Planning: Monitor growth and plan resources accordingly

  2. Update Strategy: Test updates in staging before production

  3. Disaster Recovery Plan: Document and test disaster recovery procedures

Conclusion

Setting up a private Git server provides complete control over your source code management infrastructure while offering flexibility, security, and cost savings. Whether you choose a simple SSH-based solution for personal projects, Gitolite for advanced access control, or full-featured platforms like Gitea or GitLab for team collaboration, this guide has provided comprehensive instructions for implementation.

The key to successful Git server management lies in choosing the right solution for your needs, implementing proper security measures, maintaining regular backups, and following operational best practices. Start with a simple setup and scale complexity as your requirements grow.

Your private Git server investment pays dividends through enhanced security, complete data control, and the flexibility to customize your development infrastructure exactly as needed. By following this guide's recommendations, you've established a solid foundation for reliable, secure version control that will serve your development needs for years to come.

Remember that Git server administration is an ongoing responsibility requiring regular maintenance, monitoring, and updates. Stay current with security patches, monitor performance metrics, test your backup procedures, and continuously refine your workflows to maintain a healthy, efficient Git infrastructure.

With your private Git server properly configured and maintained, you can focus on what matters most: writing great code and building exceptional software products with confidence in your version control foundation.