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.
| Forgejo | GitLab CE | |
|---|---|---|
| Minimum RAM | 512 MB, comfortable at 1 GB | 4 GB, comfortable at 8 GB |
| Components | One binary, one database | Rails, Sidekiq, Redis, Postgres, Gitaly, Puma, Nginx |
| Install | Copy a binary | Omnibus package or Helm chart |
| CI | Forgejo Actions, GitHub Actions syntax | GitLab CI, mature and extensive |
| Features | Repos, PRs, issues, packages, LFS, Actions | The above plus security scanning, boards, environments |
| Upgrade | Replace the binary, restart | Follow 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
| Use | Configuration | MassiveGRID VPS |
|---|---|---|
| Personal, a few repos | 1 vCPU / 2 GB GB / 32 GB | $4.79/mo |
| Small team, SQLite or Postgres | 2 vCPU / 4 GB GB / 64 GB | $9.58/mo |
| Team plus a CI runner | 4 vCPU / 8 GB GB / 128 GB | $19.16/mo |
| Larger team, heavy CI | 8 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.