Git is a peer-to-peer protocol at heart — a remote is just another clone of the repository — which means a VPS can host your code with nothing more than SSH and git init --bare. When you want a web UI, issue tracking, and per-user permissions, Gitea fills that role in a single ~100 MB Go binary that idles under 100 MB of RAM, while GitLab CE recommends 4 GB or more for the same job.
Memory is the deciding factor: a bare-repo setup runs comfortably on a 1 GB plan, while Gitea is happy on 2 GB. see the full specs on our VPS comparison table and match RAM to the option you choose below.
Bare Repositories vs Git Hosting Software
- Bare repository over SSH: zero daemons, zero web surface, works with any Git client. Ideal for 1–5 developers who live in the terminal.
- Gitea: lightweight web UI, pull requests, webhooks, 2FA, and CI integrations in one binary. The sweet spot for small teams.
- GitLab CE: heavyweight pipelines and compliance features, but demands 4 GB+ RAM — usually overkill on a small VPS.
Option 1: A Bare Repository over SSH
# on the VPS
mkdir -p /srv/git && cd /srv/git
git init --bare myproject.git
# on your laptop
git remote add origin ssh://[email protected]:22/srv/git/myproject.git
git push -u origin main
Create a dedicated git system user, put the repositories under /srv/git, and every developer authenticates with their own SSH key. No extra software, no web panel to patch — just Git and sshd.
Bare repositories are the raw material of Git hosting: a bare repo has no working tree, only the .git internals, which makes it safe to push to. Give each repo a .git suffix by convention (myproject.git) so tooling and humans both recognize it as a remote. Set git config receive.denyNonFastForwards true per repo if you want to force the team to use merge requests instead of force-pushing over each other.
Option 2: Installing Gitea
useradd -r -m -s /bin/bash git
curl -L -o /usr/local/bin/gitea https://dl.gitea.com/gitea/1.22/gitea-1.22-linux-amd64
chmod +x /usr/local/bin/gitea
mkdir -p /var/lib/gitea/{custom,data,log} && chown -R git:git /var/lib/gitea
Gitea runs as a single binary under the git user. Point it at a SQLite database for small teams or MariaDB/PostgreSQL past a few dozen users, and put it behind Nginx with TLS.
The install script downloads the binary, but verify the checksum from the official release page before running it — you are downloading code that will hold your entire source tree. After first launch, the web installer asks for the database, site name, and the git user; keep the install path under /var/lib/gitea so upgrades are just a binary swap plus gitea migrate.
- Run Gitea on port 3000 internally and let Nginx terminate TLS — do not expose the raw port to the internet.
- Enable the built-in 2FA for every account; the web UI is now the attack surface, not just SSH.
- Configure a backup of the Gitea data directory (
gitea dump) on a schedule — the repositories are only as safe as your backup pipeline.
SSH Keys: Locking Down Git Access
mkdir -p /home/git/.ssh && chmod 700 /home/git/.ssh
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... dev@laptop" >> /home/git/.ssh/authorized_keys
chmod 600 /home/git/.ssh/authorized_keys
- Use ed25519 keys (
ssh-keygen -t ed25519) — they are faster and shorter than RSA 2048. - Add one key per developer and revoke access by deleting that line — no shared passwords anywhere.
- Disable password authentication in
sshd_configso the only way in is a key.
For Gitea-managed repos, SSH keys are uploaded through the web UI and Gitea’s own SSH server (or its authorized_keys wrapper) handles them, which means you manage keys per-user in one place. For bare repos, keys live in /home/git/.ssh/authorized_keys. Either way, never let developers share an account — auditing who pushed what becomes impossible the moment two people share a key.
Add a restrict prefix to each key in authorized_keys (for example restrict,command="/usr/bin/git-shell -c "$SSH_ORIGINAL_COMMAND"" ssh-ed25519 ...) so a stolen key can only run Git commands, not an interactive shell on your VPS. This is the single most important hardening step for a self-hosted Git server — it turns a compromised laptop key into a non-event instead of full server access.
Repository Hooks for Automation
#!/bin/bash
# /srv/git/myproject.git/hooks/post-receive
GIT_WORK_TREE=/var/www/myproject git checkout -f
cd /var/www/myproject && systemctl reload nginx php8.3-fpm 2>/dev/null || true
- Make the hook executable with
chmod +x hooks/post-receive. - Hooks run as the
gituser, so grant that user write access to the deployment directory. - Test with an empty commit — a broken hook fails the push, which is exactly what you want to see locally.
git gc and Disk Hygiene
git -C /srv/git/myproject.git gc --auto
# crontab -e (as the git user):
0 3 * * 0 git -C /srv/git gc --auto && du -sh /srv/git/*
Repositories grow with every force-push and branch deletion; git gc --auto packs loose objects and prunes unreachable ones. Add the cron entry so housekeeping runs weekly without you remembering to do it.
Watch disk usage on the VPS as the team grows: a few dozen active repositories with CI artifacts can consume several gigabytes. Set an alert at 80% utilization and migrate to a larger data volume before the disk fills — a full disk corrupts repositories in ways git fsck cannot always repair.
Start with a bare repo and SSH keys — it is the most secure option by default because there is no web daemon to attack. If the team outgrows it, Gitea slots in without changing your remotes. When you provision the server, compare plans side by side on our comparison table to right-size RAM and disk for your repository count.
InterServer’s VPS plans include dedicated IPs and generous bandwidth — plenty for a self-hosted Git server with a team of any size. Check InterServer VPS plans and pricing to get your code off third-party hosts.




Leave a Reply
You must be logged in to post a comment.