Forgejo is a single Go binary that turns a small server into a full code forge: repositories, pull requests, issues, packages and CI, in about a gigabyte of memory. It is the Codeberg-maintained fork of Gitea, and it is what to reach for when self-hosted GitLab is more platform than you need. This guide covers sizing, the configuration that keeps it secure, Actions, and a backup that captures everything.

Forgejo is a single Go binary with an embedded web interface and SSH server. It will run a small team's repositories on a server costing less per month than a couple of coffees, and it does so without the operational weight that self-hosted GitLab brings. If you have read our guide to setting up a bare Git server, this is the layer that gives that setup pull requests, issues and CI.

MassiveGRID Ubuntu VPS: Ubuntu 24.04 LTS · Proxmox HA cluster with automatic failover · Ceph 3x replicated NVMe storage · independent CPU, RAM and storage scaling · 100% uptime SLA

Cloud VPS — from $1.99/mo
Dedicated VPS when CI runners share the box · For developers

Forgejo, Gitea or GitLab

Forgejo is a fork of Gitea, maintained by the Codeberg team, created after a governance dispute when Gitea moved to a for-profit company. Functionally they remain close, so most Gitea documentation applies. Forgejo is licensed GPLv3, releases on a predictable schedule, and is developed by a non-profit, which is the reason most people choosing between them today pick Forgejo.

ForgejoGitLab CE
Minimum RAM512 MB, comfortable at 1 GB4 GB, comfortable at 8 GB
ComponentsOne binary, one databaseRails, Sidekiq, Redis, Postgres, Gitaly, Puma, Nginx
InstallCopy a binaryOmnibus package or Helm chart
CIForgejo Actions, GitHub Actions syntaxGitLab CI, mature and extensive
FeaturesRepos, PRs, issues, packages, LFS, ActionsThe above plus security scanning, boards, environments
UpgradeReplace the binary, restartFollow the version upgrade path carefully

Choose GitLab if you need its DevOps features and can spare the memory. Choose Forgejo if you want somewhere to host code and review it, on hardware you were not planning to buy.

Sizing

UseConfigurationMassiveGRID VPS
Personal, a few repos1 vCPU / 2 GB GB / 32 GB$4.79/mo
Small team, SQLite or Postgres2 vCPU / 4 GB GB / 64 GB$9.58/mo
Team plus a CI runner4 vCPU / 8 GB GB / 128 GB$19.16/mo
Larger team, heavy CI8 vCPU / 16 GB GB / 256 GB$38.32/mo

The variable is CI, not Git. Repositories are small and cheap to serve. A build runner on the same server competes for exactly the CPU that keeps the web interface responsive, which is the argument for either a separate runner host or guaranteed cores.

Installation

Create a dedicated user, place the binary, and lay out the directories:

apt update && apt install -y git git-lfs
adduser --system --shell /bin/bash --group --disabled-password \
        --home /home/git git

VER=11.0.0
wget -O /usr/local/bin/forgejo \
  https://codeberg.org/forgejo/forgejo/releases/download/v${VER}/forgejo-${VER}-linux-amd64
chmod 755 /usr/local/bin/forgejo

mkdir -p /var/lib/forgejo/{custom,data,log} /etc/forgejo
chown -R git:git /var/lib/forgejo /etc/forgejo
chmod 750 /var/lib/forgejo
chmod 770 /etc/forgejo

Check the release page for the current version rather than copying the number above. The /etc/forgejo permissions are deliberately loose at first: Forgejo writes its config there during the web installer, and you tighten them afterwards.

Add the service unit at /etc/systemd/system/forgejo.service:

[Unit]
Description=Forgejo
After=network-online.target postgresql.service
Wants=network-online.target

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

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now forgejo

SQLite or PostgreSQL

SQLite is a legitimate choice here, not a compromise. For a single user or a handful of developers it is fast, needs no separate service, and makes backup a file copy. Its limit is write concurrency, and Git hosting is a read-heavy workload.

Move to PostgreSQL when several people push and review simultaneously, or when CI writes status updates constantly:

sudo -u postgres psql -c "CREATE USER forgejo WITH PASSWORD 'generated';"
sudo -u postgres psql -c "CREATE DATABASE forgejo OWNER forgejo;"

Switching later means a database migration, so if you expect to grow past a few active developers, start on PostgreSQL.

The Settings That Matter

Complete the web installer at port 3000, then edit /etc/forgejo/app.ini. Four things need attention:

[server]
DOMAIN           = git.example.com
ROOT_URL         = https://git.example.com/
HTTP_ADDR        = 127.0.0.1
HTTP_PORT        = 3000
SSH_DOMAIN       = git.example.com
START_SSH_SERVER = true
SSH_PORT         = 2222
SSH_LISTEN_PORT  = 2222
LFS_START_SERVER = true

[service]
DISABLE_REGISTRATION            = true
REQUIRE_SIGNIN_VIEW             = false
DEFAULT_KEEP_EMAIL_PRIVATE      = true
ENABLE_NOTIFY_MAIL              = true

[security]
INSTALL_LOCK = true

ROOT_URL has to be the public HTTPS URL, because Forgejo builds clone URLs and webhook targets from it. Get it wrong and every clone command the interface displays points somewhere unreachable.

DISABLE_REGISTRATION = true is not optional on a public address. An open Git host is found by scanners within days and used to store whatever they like.

INSTALL_LOCK = true prevents the installer from being reachable again. Left off, anyone who finds the URL can reconfigure the instance.

For SSH, the built-in server on a non-standard port is simpler than integrating with the host's sshd, and it keeps Git access independent of your admin SSH configuration. Then tighten permissions:

chmod 640 /etc/forgejo/app.ini
chmod 750 /etc/forgejo
systemctl restart forgejo

Reverse Proxy

server {
    listen 443 ssl;
    http2 on;
    server_name git.example.com;

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

    client_max_body_size 1024M;

    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;
        proxy_read_timeout 300s;
    }
}

The large body size is what allows pushing a big repository or an LFS object over HTTPS. The default is one megabyte, and the resulting failure is a 413 partway through a push.

Open only what is needed:

ufw allow 22/tcp
ufw allow 443/tcp
ufw allow 2222/tcp
ufw enable

Forgejo Actions

Forgejo Actions runs GitHub Actions workflow syntax, which means most public actions work and your team already knows the format. It needs a separate runner process, and running it on the same server as Forgejo is fine for light use and a bad idea once builds are heavy.

ENABLE_ACTIONS = true    # under [actions] in app.ini

Register a runner using a token generated in the admin interface, and run builds in Docker containers rather than directly on the host. A runner executing arbitrary repository code with host access is a route from a pull request to your server.

Backups

Forgejo has a built-in dump that captures repositories, database, configuration and attachments in one archive:

sudo -u git forgejo dump -c /etc/forgejo/app.ini \
  --file /backup/forgejo-$(date +%F).zip

That is genuinely everything, which makes it one of the better backup stories among self-hosted applications. Copy the archive off the server, and restore into a scratch instance occasionally to confirm the dump is complete.

Git's distributed nature is a partial safety net, since every clone is a copy of the history. It is not a backup of issues, pull request discussions, or CI configuration, and those are the parts nobody can reconstruct.

Storage That Does Not Lose History

A Git host holds the review discussion, issue history and CI configuration around the code. The code itself exists on every developer's laptop; the context does not.

Every MassiveGRID VPS runs on a Proxmox high-availability cluster with automatic failover, on Ceph storage that replicates every block three times across independent NVMe drives. A failing drive is invisible rather than an incident. Because resources scale independently, adding CPU for a busier CI runner does not mean paying for storage you have not filled.

Deploy a Cloud VPS from $1.99/mo, or a Dedicated VPS if builds and the web interface will share the machine. Sizing for other applications is covered in our self-hosted application requirements guide.

Further Reading