← Back to Home

Self-Hosted Git Pitfalls: Gitea vs Forgejo 2026

GiteaForgejoself-hosted-git

I deployed Gitea 1.24.4 and Forgejo 15.0.3 LTS on two separate VPS instances, used them for a month, and hit 5 real traps. This article shares those pitfalls and my comparison conclusions so you can skip the pain.

Links to VPS services in this article are affiliate links. If you purchase through them, I may earn a small commission at no extra cost to you.

Gitea or Forgejo? Background First

In late 2022, Gitea announced the formation of Gitea Ltd, a commercial company. The community worried about the project going commercial. Some core contributors forked Forgejo under community governance. Same codebase originally, but diverging since 2023:

DimensionGitea 1.24.4Forgejo 15.0.3 LTS
LicenseMITMIT
GovernanceGitea Ltd (company-led)Community (Codeberg-hosted)
CI/CDGitea Actions (GitHub Actions compatible)Forgejo Actions (GitHub Actions compatible)
Package RegistryContainer/PyPI/NPM etc.Container/PyPI/NPM etc.
FederationNoneActivityPub (experimental)
Min RAM512MB usable512MB usable
LanguageGoGo
BinarySingle-file deploySingle-file deploy

One-line verdict: Feature-wise they're nearly identical. The decision point is governance — do you trust a company-driven release cadence or a community-driven direction?

Trap 1: SSH Clone Returns Permission Denied

This is the most common and most frustrating issue. You uploaded your SSH public key, but git clone git@your-server:user/repo.git keeps failing:

Permission denied (publickey).
fatal: Could not read from remote repository.

**Root cause**: When deploying with Docker, the container's SSH service (port 22) conflicts with the host SSH. Most people map container SSH to port 2222 but forget to configure ~/.ssh/config:

Host your-server.com
    Port 2222
    User git
    IdentityFile ~/.ssh/id_ed25519

**Another common cause**: The /data/git/.ssh/authorized_keys file inside the container has wrong permissions. If the Docker volume was created by root, the container's git user (UID 1000) can't read it:

# Fix: correct permissions on the host
sudo chown -R 1000:1000 /path/to/gitea/data/git/.ssh
sudo chmod 600 /path/to/gitea/data/git/.ssh/authorized_keys

Verification:

ssh -T git@your-server.com -p 2222
# Success returns: Hi username! You've successfully authenticated...

Trap 2: Docker Networking Causes Reverse Proxy 502

You put Gitea/Forgejo in Docker, with Nginx or Caddy in front for HTTPS termination. The most common problem: Nginx returns 502 Bad Gateway.

**Cause 1**: Nginx and Gitea aren't in the same Docker network. If you deploy Gitea with docker compose, it creates a network named gitea_default by default. Nginx deployed separately or installed system-wide can't reach the Gitea container:

# docker-compose.yml key config
services:
  server:
    image: gitea/gitea:1.24.4
    networks:
      - gitnet

  nginx:
    image: nginx:1.28
    networks:
      - gitnet  # ← Must be on same network

networks:
  gitnet:
    driver: bridge

**Cause 2**: Nginx proxy_pass uses an IP address instead of the container name. Docker container IPs change on restart:

# ❌ Wrong
proxy_pass http://172.18.0.3:3000;

# ✅ Correct
proxy_pass http://server:3000;  # server is the service name in docker-compose

Trap 3: SQLite database is locked Under Concurrent Load

Gitea/Forgejo default to SQLite, which works fine for individuals or small teams. But "fine" has a way of surprising you — like when CI/CD pushes to multiple repos simultaneously:

database is locked (5) (SQLITE_BUSY)

Solution 1: Switch to PostgreSQL. This is the most thorough fix:

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_DB: gitea
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: your-strong-password
    volumes:
      - ./postgres:/var/lib/postgresql/data

**Solution 2**: If you insist on SQLite, add WAL mode and timeout in app.ini:

[database]
DB_TYPE  = sqlite3
PATH     = /data/gitea/gitea.db
SQLITE_JOURNAL_MODE = WAL
SQLITE_BUSY_TIMEOUT = 5000

WAL mode allows concurrent reads and writes, and BUSY_TIMEOUT sets the lock wait to 5 seconds. This resolves most lightweight locking issues.

Trap 4: Backup and Recovery Pitfalls

The core reason to self-host is "data stays with you." But having data doesn't mean you can restore it — I found three traps during testing:

Trap 4a: Only backed up repos, forgot database and LFS

A complete backup should include:

#!/bin/bash
# gitea-backup.sh
GITEA_HOME="/path/to/gitea"
BACKUP_DIR="/backups/gitea/$(date +%Y%m%d)"

mkdir -p "$BACKUP_DIR"

# 1. Use built-in backup command (Gitea 1.24+)
docker exec -u git server gitea dump -c /data/gitea/conf/app.ini

# 2. Or manually back up critical directories
cp -r "$GITEA_HOME/data/git/repositories" "$BACKUP_DIR/repositories"
cp -r "$GITEA_HOME/data/gitea/lfs" "$BACKUP_DIR/lfs"
cp "$GITEA_HOME/data/gitea/gitea.db" "$BACKUP_DIR/"  # SQLite
cp -r "$GITEA_HOME/data/gitea/conf" "$BACKUP_DIR/conf"

Trap 4b: UID/GID mismatch on restore

When restoring on a new server, if the container's git user UID changes, SSH and repository permissions break:

# Verify UID before restore
docker exec server id git
# uid=1000(git) gid=1000(git)

# If mismatched, specify in docker-compose.yml
services:
  server:
    user: "1000:1000"

Trap 4c: Built-in dump command paths differ in rootless mode

# Root mode
docker exec -u git server gitea dump -c /data/gitea/conf/app.ini

# Rootless mode (note path differences)
docker exec server gitea dump -c /data/gitea/conf/app.ini
# Output file is inside container at /app/gitea/gitea-dump-*.zip

Trap 5: Forgejo Actions Runner Connection Failures

Forgejo Actions are syntax-compatible with GitHub Actions, but the Runner often fails to connect on first deployment:

**Cause 1**: Runner registration used localhost, but Runner is in another container:

# ❌ Runner container can't reach localhost:3000
forgejo-runner register --instance http://localhost:3000

# ✅ Use inter-container communication
forgejo-runner register --instance http://server:3000

**Cause 2**: Forgot to enable Actions in app.ini:

[actions]
ENABLED = true

This configuration is required for both Gitea and Forgejo — it's disabled by default. Many people configure the Runner only to find the server side isn't enabled.

My Final Choice

After a month of deployment, I chose Forgejo 15.0 LTS for these reasons:

1. LTS support until July 2027 — no frequent upgrades needed

2. Community governance gives me more confidence in future direction

3. Feature differences between the two are negligible — neither choice is wrong

But if you prioritize "closest upstream to GitHub," Gitea is the safer bet. Both are mature projects with no wrong answer.

Quick Deploy Reference

Minimal Docker Compose (Forgejo + PostgreSQL):

version: "3"
services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:15.0
    container_name: forgejo
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always
    volumes:
      - ./forgejo:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "2222:22"
    depends_on:
      - db

  db:
    image: postgres:17
    restart: always
    environment:
      - POSTGRES_DB=forgejo
      - POSTGRES_USER=forgejo
      - POSTGRES_PASSWORD=change-me-now
    volumes:
      - ./postgres:/var/lib/postgresql/data

Deploy command:

docker compose up -d
# Visit http://your-server:3000 to complete initial setup
# Remember to specify port 2222 for SSH cloning

If your VPS has room to spare (2GB+ RAM), consider running OpenClaw alongside for AI agent automation — 👉 Get started: https://platform.minimaxi.com/subscribe/token-plan?code=E5yur9NOub&source=link

FAQ

Q: Can I migrate between Gitea and Forgejo?

A: Yes. Their database formats are compatible. To migrate from Gitea to Forgejo, back up Gitea data, then start Forgejo at the same version pointing to the same database. Always verify in a test environment first.

Q: Does a solo developer need Actions?

A: Depends on your automation needs. If you use GitHub Actions for CI/CD, self-hosted Git Actions can migrate workflow files seamlessly. If you just store code and do code reviews, Actions isn't necessary.

Q: Can a 512MB VPS handle it?

A: Barely usable with SQLite and Actions disabled. I recommend at least 1GB. With PostgreSQL, 2GB is more comfortable. Try Vultr's NVMe instances starting at $2.5/mo: https://www.vultr.com/?ref=9890714

📌 This article was AI-assisted generated and human-reviewed | TechPassive — An AI-driven content testing site focused on real tool reviews

🔗 Recommended Tools

These are carefully selected tools. Using our affiliate links supports us to keep producing quality content:

☁️ DigitalOcean Cloud ⚡ Vultr VPS 📚 WordPress Books 🔍 WordPress SEO Books 🌐 Web Hosting Books 🐳 Docker Books 🐧 Linux Books 🐍 Python Books 💰 Affiliate Marketing 💵 Passive Income Books 🖥️ Server Books ☁️ Cloud Computing Books 🚀 DevOps Books ⭐ MiniMax Token Plan 🔍 Cloud Search
← Back to Home