
Git hosting looks simple until you need real control. You may need private repos for client work, predictable costs, custom SSH rules, and backups you can actually restore.
If you want that without a heavyweight platform, self-host Gitea on a VPS. You get a clean web UI, fast Git over SSH, and an admin area that stays manageable as you add users and organizations.
This guide is for people running infrastructure, not just side projects. It explains where Gitea fits, what it can replace, and what “production-ready” means in practice.
That includes TLS termination, SSH Git, backups, and upgrades you can repeat without surprises.
Why developers self-host Gitea (and who it’s for)
Gitea is a self-hosted Git service for teams that want GitHub-style workflows, without handing over source code (or identity) to a third party. It’s a good fit if you:
- Maintain private repositories for a product, an agency, or internal tooling.
- Need predictable performance for pushes and pulls (especially for monorepos or large assets tracked in Git LFS).
- Prefer your own auth rules (local users, LDAP, OAuth/OIDC) and your own audit trail.
- Want low operational load compared to larger platforms.
Gitea is open source (MIT license). It includes issues, pull requests, wiki, webhooks, releases, Git LFS, and actions-style automation via companion runners (optional).
If you already run CI elsewhere, Gitea still works well as your Git “source of truth.”
Self-host Gitea vs hosted Git services (GitHub/GitLab/Bitbucket)
Hosted Git services are convenient, and for plenty of teams they’re the right choice. The trade-offs show up when you care about data locality, compliance requirements, or recurring costs that rise with usage.
- Control: Self-hosting lets you set retention, IP allowlists, SSH policy, and backup storage. Hosted providers set the defaults. Exceptions often mean enterprise plans.
- Performance predictability: A modest VPS with NVMe can feel snappier than a shared SaaS tier. This is most noticeable for repeated fetches and CI clones in the same region.
- Operational responsibility: You own upgrades, disk growth, and incident response. The upside is fewer surprise platform changes.
- Feature depth: GitHub and GitLab are large ecosystems. Gitea stays focused on core Git hosting and collaboration, and it stays lightweight.
If you want Git hosting that’s small, fast, and under your control, Gitea usually lands in the sweet spot.
Planning the VPS: sizing, storage, and network basics
Gitea doesn’t need much CPU. It does need disk I/O and storage headroom for repo growth, attachments, and Git LFS.
Size around your repo footprint today. Then add room for next quarter.
- Minimum (personal/small team): 1 vCPU, 2 GB RAM, 40–80 GB NVMe storage.
- Comfortable (team use): 2 vCPU, 4 GB RAM, 80–160 GB NVMe storage.
- Busy instance (many repos/users): 4 vCPU, 8+ GB RAM, NVMe with growth plan.
For production, pick NVMe-backed storage and a provider that makes snapshots and volume expansion painless. A VPS is a solid starting point.
If you host huge monorepos with lots of concurrent activity, a dedicated server can be worth it.
If you want a clean baseline without spending time on low-level tuning, start with a HostMyCode VPS.
If you’d rather offload patching and core maintenance, managed VPS hosting is the lower-stress route.
Deployment approach: Docker Compose with a real reverse proxy
Gitea runs fine as a single binary. Docker Compose makes installs consistent across servers, and upgrades are usually cleaner.
Two choices matter most:
- Where TLS terminates
- How you expose SSH Git
A layout that works well on a VPS:
- Gitea container on an internal Docker network
- Reverse proxy (Nginx or Caddy) on the host, terminating TLS and proxying to Gitea’s HTTP port
- SSH Git either via the container’s SSH server on a high port, or by using host SSH and forced commands (the container approach is simpler)
This post uses Nginx because it’s common on VPS nodes and predictable under load.
Docker Compose: a production-friendly baseline
Create a working directory such as /opt/gitea. Keep the layout boring and obvious:
/opt/gitea/compose.yml/opt/gitea/data/(Gitea data, repos, config)/opt/gitea/db/(optional if you run an external DB; many small installs use SQLite inside the data dir, but PostgreSQL is safer for growth)
On a new Ubuntu or Debian VPS, install Docker Engine and the Compose plugin:
sudo apt update
sudo apt install -y docker.io docker-compose-plugin
sudo systemctl enable --now docker
Then create the directories:
sudo mkdir -p /opt/gitea
sudo mkdir -p /opt/gitea/data
sudo chown -R root:root /opt/gitea
A Compose example using PostgreSQL (recommended once you have multiple users and many repos):
services:
db:
image: postgres:16
container_name: gitea-db
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=change-this-password
- POSTGRES_DB=gitea
volumes:
- ./db:/var/lib/postgresql/data
restart: unless-stopped
gitea:
image: gitea/gitea:latest
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=change-this-password
- GITEA__server__DOMAIN=git.example.com
- GITEA__server__ROOT_URL=https://git.example.com/
- GITEA__server__SSH_DOMAIN=git.example.com
- GITEA__server__SSH_PORT=2222
volumes:
- ./data:/data
ports:
- "127.0.0.1:3000:3000"
- "2222:22"
depends_on:
- db
restart: unless-stopped
Notes you’ll care about later:
- Binding
3000to127.0.0.1keeps the web UI private behind Nginx. - Publishing
2222:22gives you Git over SSH without touching your host’s SSH daemon. ROOT_URLmust match your public URL, includinghttps://.
Bring it up:
cd /opt/gitea
sudo docker compose up -d
sudo docker compose ps
Reverse proxy + HTTPS: Nginx with Let’s Encrypt
You can terminate TLS with Caddy or Nginx. Nginx is a sensible default if you already host multiple sites on the same VPS.
It also gives you consistent logs, rate limits, and predictable behavior under load.
Install Nginx and Certbot on Ubuntu/Debian:
sudo apt install -y nginx certbot python3-certbot-nginx
Create an Nginx site file at /etc/nginx/sites-available/gitea.conf:
server {
listen 80;
server_name git.example.com;
location / {
proxy_pass http://127.0.0.1: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;
# Git pushes can be large; don’t choke on a default limit.
client_max_body_size 200m;
}
}
Enable the site and reload:
sudo ln -s /etc/nginx/sites-available/gitea.conf /etc/nginx/sites-enabled/gitea.conf
sudo nginx -t
sudo systemctl reload nginx
Issue the certificate:
sudo certbot --nginx -d git.example.com
If you prefer wildcard certificates (handy when you run multiple internal tools), follow the DNS-based approach in Add Let’s Encrypt Wildcard SSL With Caddy and DNS and adapt it to your proxy choice.
SSH Git: make clones and pushes predictable
With the Compose setup above, users will connect like this:
- HTTPS:
https://git.example.com/youruser/yourrepo.git - SSH:
ssh://git@git.example.com:2222/youruser/yourrepo.git
One small tweak prevents a lot of confusion. Set Gitea’s advertised SSH port to 2222 (done via GITEA__server__SSH_PORT).
That way, the UI prints clone URLs that actually work.
Using port 2222 also avoids colliding with your host’s SSH on port 22. Keep 22 for server administration. Use 2222 for Git traffic.
First-run checklist: what to change before inviting your team
Once the web UI is up, slow down on the install screen. A few early choices save cleanup work later:
- Disable open registration unless you truly want public sign-ups.
- Set a sane default branch name and repo visibility defaults for your org.
- Enable email settings only if you can deliver mail reliably; otherwise notifications turn into noisy log errors.
- Configure backups before day one data. It’s easy to postpone once repos start showing up.
If you need outbound email for notifications, don’t start with “random SMTP from a VPS.” Many providers rate-limit or block port 25.
Plan delivery instead. Use an SMTP relay, authenticated submission, and correct DNS records.
Backups that restore: repositories, database, and secrets
For Gitea, “backup the repos” isn’t enough. You need three buckets:
- Application data:
/opt/gitea/data(includes repositories, attachments, config) - Database: PostgreSQL data directory
/opt/gitea/dbor a logical dump - TLS + proxy config: Nginx site file and Let’s Encrypt directory (
/etc/letsencrypt) if you want full bare-metal recovery speed
A reliable pattern is a nightly logical database dump plus a file-level backup of /opt/gitea.
This keeps restores portable, even if you move between servers.
Create a backup script like /usr/local/sbin/backup-gitea.sh:
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/var/backups/gitea"
TS="$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$BACKUP_DIR"
# Database dump
cd /opt/gitea
sudo docker compose exec -T db pg_dump -U gitea gitea | gzip > "$BACKUP_DIR/db-$TS.sql.gz"
# App data (repos, config, attachments)
tar -C /opt -czf "$BACKUP_DIR/opt-gitea-$TS.tar.gz" gitea
# Keep it simple: prune old backups (example: 14 days)
find "$BACKUP_DIR" -type f -mtime +14 -delete
Schedule it via cron:
sudo crontab -e
# Nightly at 02:30 UTC
30 2 * * * /usr/local/sbin/backup-gitea.sh
Two practical cautions:
- Test restores monthly. A backup you never restore is a guess. Spin up a disposable VPS and run a full recovery.
- Store offsite. Disk failure and “rm -rf” mistakes happen. Push
/var/backups/giteato object storage or a second server.
If you want a repeatable mental model for backups with restore tests, the workflow is similar to the one in Set Up PostgreSQL Backups With pg_dump + Restore Test.
Updates: low-drama upgrades you can repeat
Docker upgrades are straightforward, but don’t treat them as “pull and pray.” Use a routine you can run the same way every time:
- Take a fresh backup (or at least a DB dump).
- Pull the new images.
- Recreate containers.
- Watch logs for migrations and errors.
cd /opt/gitea
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs -f --tail=200 gitea
Check disk space after upgrades. Git hosting grows quietly.
Then one day the filesystem hits 100% and everything “mysteriously” fails. A simple alert on / and /opt usage is cheap insurance.
Security quick wins for a public-facing Gitea
A public login page will attract password spraying. You don’t need a complicated security stack, but you do need the basics in place.
- Firewall: Only open
22/tcp(server admin),80/tcp,443/tcp, and your Git SSH port (2222/tcp). Close everything else. - Fail2ban: If Nginx is your front door, protect it. Use access logs to ban obvious brute-force patterns.
- Admin separation: Don’t reuse your sysadmin SSH keys as your Git keys. Keep those roles separate.
- Backups access: Don’t leave backups world-readable on the same server. Use permissions and encryption.
If you want a straightforward baseline for banning noisy IPs, adapt the patterns from Set Up Fail2ban for Nginx on Ubuntu VPS.
Common pitfalls (the stuff that wastes an afternoon)
- Wrong ROOT_URL: If your
ROOT_URLdoesn’t match the public domain and scheme, you’ll get broken links, redirect loops, or incorrect clone URLs. - Proxy headers missing: Without
X-Forwarded-Proto, Gitea may generatehttp://links even behind HTTPS. - Git push rejected due to size limits: Nginx defaults can block large requests. Set
client_max_body_sizeto match your repo realities. - Disk fills up from LFS: Git LFS can balloon quickly. Decide early where big binaries belong.
- SSH port confusion: If you use a non-standard port, users must include it. Make sure the UI advertises it correctly.
Where Gitea fits in a bigger self-hosted stack
Git hosting is usually the first step, not the last. Once you control your repos, you’ll often add:
- CI: Pair Gitea with an open CI runner (or a separate system) for builds and deployments.
- Status pages/monitoring: Watch the VPS for disk and memory pressure.
- Backups + migrations: Decide how you’d move to a larger VPS or a dedicated server.
If CI is next on your list, HostMyCode covers one common pairing in Self-Host Woodpecker CI With Gitea on a VPS.
Even if you choose a different CI tool, the infrastructure shape stays the same. Isolate services, terminate TLS cleanly, and back up the data stores.
Summary: private Git hosting that stays boring
Gitea solves a plain problem. You want private Git hosting you control, without inheriting a massive platform.
Docker Compose keeps deployments repeatable. Nginx + Let’s Encrypt handles HTTPS cleanly. A real backup plan keeps the business risk down.
If you’re ready to run it on infrastructure you control, a HostMyCode VPS is an easy place to start.
If you’d rather spend your time on code than server maintenance, managed VPS hosting can take the day-to-day load off.
If you want private Git hosting without vendor lock-in, Gitea runs well on a VPS with NVMe storage and stable network performance. Start with a right-sized HostMyCode VPS, or choose managed VPS hosting if you want help with patching, monitoring, and steady day-to-day operations.
FAQ
Can I run Gitea with SQLite instead of PostgreSQL?
Yes. SQLite is fine for personal use and small teams. If you expect growth (more users, more issues/PR activity), PostgreSQL lowers risk and handles concurrent writes better.
Do I need port 2222 open to the internet?
Only if you want Git over SSH from outside your network. If your team uses HTTPS clones and pushes, you can keep SSH closed.
Many teams still open it because SSH keys are convenient and fast.
What should I back up for a reliable restore?
Back up /opt/gitea/data, your database (preferably as a logical dump), and your reverse-proxy/TLS configuration. Then test a full restore on a throwaway VPS.
How do I move Gitea to a bigger server later?
Take a final DB dump, stop the stack, copy /opt/gitea to the new VPS, restore the DB, and bring the stack up. If you want the move to be hands-off, consider using HostMyCode migrations to reduce downtime risk.
Is Gitea enough, or do I need a full DevOps platform?
If your goal is private Git hosting plus code review basics, Gitea is usually enough. Add CI as a separate component when you need it, instead of adopting a bigger platform upfront.