Why this question is urgent in 2026
CVE-2026-59774 was published on August 4, 2026. It affects Gitea versions 1.22.1 through 1.27.0 — CVSS score 9.9, 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.
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 — same syntax as Gitea Actions (GitHub Actions-compatible), compatible with Woodpecker CI and Docker runners, running in production on Codeberg since 2024
- Forgejo v16.0 in July 2026 — multi-line review comments, external JWT authentication for the API, SSRF hardening, avatar bandwidth reduction
- GitLab CE still complete but heavy — the community version includes CI/CD, container registry, security scanning, and Pages; the cost: 4 GB RAM minimum, 8 GB recommended
- Five independent comparisons published in 2026 converge on the same hierarchy: 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: 4 GB RAM is the absolute minimum to start, 8 GB for comfortable use, with Postgres, Redis, Gitaly, and Sidekiq running in parallel. Plan for at least 4 vCPU and 50 GB of SSD storage. 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
| Criterion | Forgejo / Gitea | GitLab CE |
|---|---|---|
| Minimum RAM | 512 MB – 1 GB | 4 GB (8 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: opt-in beta · Gitea: not planned | No |
| Migration from Gitea | Forgejo: same DB schema, one command · GitLab: API importer or migrator | 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. Both projects have shared the same database schema since the beginning, and Forgejo maintains backward compatibility. 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 (migration is tested from recent versions — a very old instance may need an intermediate update), and verify that your CI runners use the same syntax (Forgejo Actions and Gitea Actions are compatible). Migration in the other direction — to GitLab CE — is longer: GitLab offers an API import tool (repositories, issues, merge requests, 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).
Create directories and the Compose file
Create a dedicated directory: mkdir -p /opt/forgejo && cd /opt/forgejo. Then create a compose.yml file with a forgejo service pointing to the image codeberg.org/forgejo/forgejo:latest, a persistent volume for /data, ports 3000:3000 (HTTP) and 2222: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 with docker compose ps and view logs with docker compose logs -f forgejo. The installation interface is available at http://<vps-ip>:3000.
Configure Nginx as HTTPS reverse proxy
Install Nginx and Certbot: apt install -y nginx python3-certbot-nginx. Create a virtual host for git.your-domain.com that proxies to http://localhost:3000 with 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 with curl -I https://git.your-domain.com that the response code is 200.
Complete installation via the web interface
Open https://git.your-domain.com in 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=true in compose.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: docker run -d --name forgejo-runner -v /var/run/docker.sock:/var/run/docker.sock codeberg.org/forgejo/runner:latest daemon --config config.yml.
Verify basic security
Check that FORGEJO__server__LOCAL_ROOT_URL does not point to an exposed internal IP (SSRF vector). Since Forgejo v16.0, SSRF hardening is active by default for Git mirroring — check FORGEJO__migrations__ALLOW_LOCALNETWORKS in your config if you need to mirror internal repositories.
For an existing Gitea instance affected by CVE-2026-59774 (versions 1.22.1 to 1.27.0): migrating to Forgejo and upgrading to Gitea 1.27.1 both fix the vulnerability. If you choose to stay on Gitea, apply the patch immediately. If you have been considering Forgejo for a while, migration resolves both issues in one operation: patch by migrating rather than upgrading then migrating. In all cases, invalidate INTERNAL_TOKEN and SECRET_KEY in app.ini after any suspected exposure, and rotate your users' personal access tokens.
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— Forgejo looks for SSH keys in/data/gitea/.ssh; if the volume is not mounted or permissions are wrong, rundocker compose exec forgejo forgejo admin regenerate keysto regenerate themx509: 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
Which tool to choose for your situation
For a new installation in 2026: Forgejo is the default choice in virtually all independent comparisons published this year. Community governance, security fixes independent of Gitea Ltd., 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. 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. GitLab CE is the right choice when your team needs the entire DevOps chain in a single installation — security scanning, pages, deployment environments, DORA metrics — 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.