Why this question is urgent in 2026
CVE-2026-59774 was published on August 2, 2026 in Gitea's security advisory. It affects Gitea versions 1.22.1 through 1.27.0 — CVSS score 9.8, critical. The attack vector: the #+INCLUDE directive in the Org-mode renderer allows an unauthenticated attacker to read arbitrary files accessible to the Gitea service account, including app.ini and internal tokens. Exposure of INTERNAL_TOKEN then opens the path to Git hook injection and remote code execution. The fix is in version 1.27.1. If your instance is running an earlier version and is not isolated from the public network, patching is the first action. The second is deciding whether to stay on Gitea or whether this situation accelerates a migration you had been putting off. Forgejo, which maintains its own security fixes independently of Gitea Ltd., had already fixed the same Org-mode rendering flaw in its v16.0.2 and v15.0.6 releases, published on July 30, 2026, before Gitea's advisory.
What has changed since 2024 and why it matters
- Forgejo has been a hard fork since early 2024 — the codebase has diverged from Gitea; both projects still share most of their foundations, but development trajectories now clearly differ
- Community governance at Forgejo — managed by a non-profit association, no contributor agreement required by a parent company; security fixes arrive independently
- Forgejo Actions in production — syntax close to GitHub Actions, like Gitea Actions (Forgejo warns that a GitHub workflow often needs minor tweaks), run by Docker runners; Woodpecker CI remains an alternative that integrates with Forgejo, and Codeberg still offers hosted Actions only in a limited fashion
- Forgejo v16.0 in July 2026 — multi-line review comments (line-range selection in the diff, inline threads), external JWT authentication for the API (lets external systems authenticate without permanent tokens), SSRF hardening for Git mirroring (
[migrations]rules now apply everywhere and Git no longer follows redirects), avatar bandwidth reduction; a non-LTS branch, no longer maintained since October 29, 2026: its features carry over into Forgejo v17.0 (October 2026) - Forgejo v15.0 in April 2026 — simplified runner registration workflow via the web interface, repository-scoped tokens, 100th Forgejo release
- GitLab CE still complete but heavy — the community version includes CI/CD, container registry, security scanning, and Pages; the cost: 8 GB RAM minimum, 16 GB recommended
- The hierarchy that emerges in 2026: Forgejo for new installs, Gitea if you are already on it and patched, GitLab CE if you need a full DevOps platform in a single install
Prerequisites depending on your choice
Forgejo and Gitea are written in Go and ship as a single static binary. A modest instance (up to twenty repositories, a few CI runners) runs comfortably on 1 vCPU and 1 GB of RAM; a team instance (active CI runners, LFS, package registry) will prefer 2 vCPU and 2 to 4 GB. GitLab CE is in a different league: its documentation sets the floor at 8 GB of RAM and recommends 16 GB, with Postgres, Redis, Gitaly, and Sidekiq running in parallel. It calls for 8 vCPU and at least 40 GB of SSD storage for the application, plus the size of your repositories. For all three tools, you need a domain pointing to your VPS, ports 22 (SSH Git), 80 and 443 open, and a reverse proxy (Nginx or Caddy) for HTTPS.
Forgejo vs Gitea vs GitLab CE — 2026 comparison table
Scroll the table
| Criterion | Forgejo / Gitea | GitLab CE |
|---|---|---|
| Minimum RAM | No official minimum (1 GB for a small team) | 8 GB (16 GB recommended) |
| Distribution format | Single static binary or Docker image | Omnibus (multi-process) or Helm chart |
| Native CI/CD | Forgejo Actions / Gitea Actions (GitHub Actions syntax) | GitLab CI/CD, shared or dedicated runners |
| Governance | Forgejo: non-profit association (Codeberg) · Gitea: Gitea Ltd. | GitLab Inc. (open-core, CE free) |
| Container registry | Yes (packages + container registry) | Yes, more complete |
| Federation / ActivityPub | Forgejo: experimental, disabled by default · Gitea: not planned | No |
| Independent security fixes | Forgejo: yes (Org-mode flaw fixed in v16.0.2, July 2026) · Gitea: via Gitea Ltd. | Via GitLab Inc. |
| v16 features (July 2026) | Forgejo: multi-line reviews, external JWT, stronger SSRF protection · Gitea: 1.27.1 patch only | N/A |
| Migration from Gitea | Forgejo: direct database takeover up to Gitea 1.22, beyond that repository-by-repository migration · GitLab: Gitea importer | Partial import (repos, issues, PRs) |
| Ideal for | Teams of 1 to 50 developers, modest VPS, lightweight CI | Organization wanting everything in one install, available infra budget |
Migrating from Gitea to Forgejo: what it actually involves
The Gitea → Forgejo migration is often described as "one command" — that is true in the simple case. But only up to Gitea 1.22: Forgejo states that Gitea 1.22 is the last version that can be upgraded transparently. Since Gitea 1.23, the database schemas have diverged, and the official route is to migrate repositories one at a time with Forgejo's built-in migration tool. For an instance on 1.22.x or earlier, the process comes down to stopping the Gitea container, pointing the same data volume at a Forgejo container, and starting it up. Forgejo detects the existing schema and applies its own migrations. A few things to check first: back up app.ini and the database, note the current Gitea version (the upgrade first goes through Forgejo v10.0, then on to a more recent version), and review your CI workflows (Forgejo Actions and Gitea Actions have close syntaxes: test them on one repository before switching). Migration in the other direction — to GitLab CE — is longer: GitLab offers a Gitea importer (repositories, issues, pull requests, milestones, labels), but CI pipelines must be rewritten in GitLab CI syntax.
Deploy Forgejo on a VPS with Docker and Nginx
Prepare the VPS
Update the system and install Docker and Docker Compose:
apt update && apt install -y docker.io docker-compose-plugin. Open the necessary ports:ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw allow 2222/tcp(2222 for SSH Git if you do not want to expose the system's port 22). Note: ports published by Docker bypass ufw rules, so only publish what needs to be published (next step).Create directories and the Compose file
Create a dedicated directory:
mkdir -p /opt/forgejo && cd /opt/forgejo. Then create acompose.ymlfile with aforgejoservice pointing to the imagecodeberg.org/forgejo/forgejo:17(major-version tag, as in the official documentation), a persistent volume for/data, ports127.0.0.1:3000:3000(HTTP, reachable by Nginx only) and2222:22(SSH), and minimal environment variables (FORGEJO__server__DOMAIN=git.your-domain.com,FORGEJO__server__SSH_PORT=2222).Start Forgejo
Launch the service:
docker compose up -d. Check that the container is active withdocker compose psand view logs withdocker compose logs -f forgejo. Port 3000 only listens on the machine itself: check withcurl -I http://127.0.0.1:3000, and only complete the installation wizard once HTTPS is in place (step 6), so that nobody completes it before you.Configure Nginx as HTTPS reverse proxy
Install Nginx and Certbot:
apt install -y nginx python3-certbot-nginx. Create a virtual host forgit.your-domain.comthat proxies tohttp://localhost:3000with the standard proxy headers (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).Obtain the TLS certificate
Run
certbot --nginx -d git.your-domain.com. Certbot configures the HTTP→HTTPS redirect and schedules automatic renewal. Verify withcurl -I https://git.your-domain.comthat the response code is200.Complete installation via the web interface
Open
https://git.your-domain.comin a browser. The installation wizard asks for the database type (SQLite to start, PostgreSQL for production), the instance URL, and SSH parameters. Create the administrator account, then disable public registration if the instance is private (FORGEJO__service__DISABLE_REGISTRATION=trueincompose.yml).Enable a CI runner (optional)
Forgejo v15.0 (April 2026) introduced a simplified runner registration workflow via the web interface. Go to Site Administration → Actions → Runners, click Create Runner and copy the registration command. Launch the runner in a second container, next to a
docker:dindservice that runs the jobs, as in the Compose example of the official documentation (imagedata.forgejo.org/forgejo/runner:13, commandforgejo-runner daemon --config runner-config.yml). Avoid mounting/var/run/docker.sock: workflows would get the equivalent of root access to the host.Verify basic security (v16 update)
Forgejo v16.0 hardened SSRF protection for Git mirroring: the
[migrations]settings (ALLOWED_DOMAINS,BLOCKED_DOMAINS,ALLOW_LOCALNETWORKS) now apply to all migration and mirroring operations, and Git no longer follows HTTP redirects.FORGEJO__migrations__ALLOW_LOCALNETWORKSremainsfalseby default: if you need to mirror internal repositories on the local network, declare them inFORGEJO__migrations__ALLOWED_DOMAINSrather than opening the whole local network, and assess the impact. LeaveFORGEJO__server__LOCAL_ROOT_URLat its default value (the local address internal processes use to reach the web service), unless SSH runs on another machine. For instances migrated from Gitea affected by CVE-2026-59774: regenerateINTERNAL_TOKEN(forgejo generate secret INTERNAL_TOKEN), then rotate your users' personal access tokens. Do not replaceSECRET_KEYlightly: it encrypts 2FA secrets among other things, which the new key could no longer decrypt.
For an existing Gitea instance affected by CVE-2026-59774 (versions 1.22.1 to 1.27.0): upgrading to Gitea 1.27.1 or a later version fixes the vulnerability, and it is the first thing to do. Migrating to Forgejo is only a shortcut from Gitea 1.22.x, the last version whose database Forgejo takes over; from Gitea 1.23 onwards, patch Gitea first, then migrate repository by repository if you choose Forgejo. By migrating to Forgejo v16.0+, you also gain SSRF hardening for mirroring, multi-line review comments, and external JWT authentication — features unavailable in Gitea 1.27.1. In all cases, after any suspected exposure, regenerate INTERNAL_TOKEN, rotate your users' personal access tokens and your OAuth secrets, and only change SECRET_KEY knowing that 2FA secrets encrypted with the old key will become unreadable.
Troubleshooting: common installation errors
The errors below cover the most frequent cases during a first deployment or migration.
Common errors and their fixes
level=fatal msg="Failed to initialize ORM engine" error="Error 1071: Specified key was too long"(MySQL/MariaDB) — the database collation is notutf8mb4: create the database withCHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ciand restartFailed to initialize SSH: ssh: no key found— an SSH host key is empty or unreadable: check that the/datavolume is mounted and owned by the UID/GID declared in Compose (1000:1000 by default), then delete the empty key file and restart the container so it is recreated;forgejo admin regenerate keysonly regenerates theauthorized_keysfilex509: certificate signed by unknown authorityon outgoing webhooks — your instance is calling an internal service with a self-signed certificate; addFORGEJO__webhook__SKIP_TLS_VERIFY=trueonly in dev, or mount the CA certificate in the container viaSSL_CERT_FILEError response from daemon: conflict: unable to delete imagewhen updating — stop the container first (docker compose down), remove the image (docker image rm codeberg.org/forgejo/forgejo), then repull (docker compose pull) before restarting- CI runners stuck on
waiting for available runnerafter registration — check that the runner can reach the Forgejo instance:curl -s https://git.your-domain.com/api/v1/versionfrom the runner container should return a JSON with the version; if the domain does not resolve from the container, add anextra_hostsentry in Compose or use the VPS IP address - Repository mirroring rejected after migration to v16 — Forgejo v16.0 strictly applies the
[migrations]settings, withFORGEJO__migrations__ALLOW_LOCALNETWORKS=falseby default: a mirror pointing to a local network address, or served behind an HTTP redirect, may be rejected; if your mirror targets an internal GitLab or Gitea, declare its domain inFORGEJO__migrations__ALLOWED_DOMAINSrather than opening the whole local network
Which tool to choose for your situation
For a new installation in 2026: Forgejo is the default choice we recommend. Community governance, security fixes independent of Gitea Ltd. (illustrated by the Org-mode flaw of CVE-2026-59774, fixed in Forgejo as early as July 30, 2026), compatibility with existing tooling (runners, webhooks, tokens), and the same memory footprint as Gitea combine all the arguments without notable trade-offs for a new project. With Forgejo v16.0, multi-line review comments and external JWT authentication address two functional gaps that sometimes justified choosing GitLab CE. If you are already running an up-to-date Gitea instance (1.27.1+) and it meets your needs, migration is not urgent — but the next time you are considering a major update, evaluate Forgejo at that point, bearing in mind that since Gitea 1.23 the move is done repository by repository. GitLab CE is the right choice when your team needs the entire DevOps chain in a single installation — security scanning, pages, deployment environments, container registry — and you have the infrastructure to run it comfortably. On a modest VPS, it is not the right tool. To go further: our detailed Forgejo installation guide, setting up a CI pipeline with Woodpecker, and the article on initial Linux server hardening complement this comparison.