Hardening a Self-Hosted Git Server: Registration, Isolation, and Secrets
🛡️ Security Intermediate 5 min read

Hardening a Self-Hosted Git Server: Registration, Isolation, and Secrets

A practical hardening pass for Gitea and similar self-hosted forges: close registration, shrink the service account's reach, isolate the container, and limit what a compromise can steal.

Published: August 26, 2026 • Updated: August 26, 2026
giteaself-hostedserver hardeningdevops security

Why This Matters Now

The August 2026 exploitation of Gitea CVE-2026-60004 followed a script that should worry anyone running a forge on a VPS: a scanner found an instance with open registrationOpen Registration🛡️A configuration in which any visitor can create an account on a self-hosted service without admin approval. It converts authenticated-only vulnerabilities into effectively unauthenticated ones, because the attacker can create the account the exploitExploit🛡️Code or technique that takes advantage of a vulnerability to cause unintended behavior, such as gaining unauthorized access. requires., created an account and a repository, exploited a patched-but-not-applied bug, and ran a miner as the service user inside the container. Total active time was around eleven seconds. Nothing about the attack was targeted. The instance was simply reachable, default-configured, and a month behind on updates.

This guide is the hardening pass that would have blunted or prevented that outcome. It uses Gitea's configuration names because that is the product in the news, but the structure applies to GitLab, Gogs, Forgejo, and anything else that runs Git operations as a service accountService Account🛡️A non-human operating system or application account under which a service runs. Its permissions define the blast radius of any exploit against that service, since attacker code executes with the service account's access to files, secrets, and the network. against user-controlled repositories. The reason those operations are dangerous is covered in why git hooks are a server-side attack surface; this piece is about the controls.

Step 1: Close the Front Door

Gitea's defaults favor a public community server, not a private team. Per the configuration documentation, DISABLE_REGISTRATION defaults to false, REGISTER_EMAIL_CONFIRM defaults to false, and ENABLE_OPENID_SIGNUP defaults to the inverse of DISABLE_REGISTRATION, so it is on whenever registration is on. REQUIRE_SIGNIN_VIEW also defaults to false, which means anonymous visitors can browse public content and use the API.

For a team server, the target state is:

  • DISABLE_REGISTRATION set to true. Admins create accounts; nobody else does. This single change removes the free write-access path that turned the Gitea bug from authenticated to effectively unauthenticated.
  • REQUIRE_SIGNIN_VIEW set to true unless you deliberately publish public repositories. It keeps anonymous traffic off the API surface.
  • If you must allow self-service signup, turn on REGISTER_EMAIL_CONFIRM and review whether OpenID signup is genuinely needed; disable it if not.

These live in the [service] section of app.ini (custom/conf/app.ini, or /etc/gitea/conf/app.ini on package installs). The documentation is explicit that a full restart is required for changes to take effect. Restart, then verify by loading the registration page while logged out.

Step 2: Put the Service Where It Cannot Reach Anything Important

Every exploit against a forge runs as the forge's service account. Its reach defines the damage. The Gitea advisory lists what that reach typically includes: app.ini and the secrets in it, environment variables carrying credentials, mounted repositories, the database, and OAuthOAuth🛡️An open standard authorization protocol that allows applications to access user resources without exposing passwords, using tokens instead of credentials. or integration tokens.

Reduce that surface deliberately:

  • Run the forge as a dedicated unprivileged user with no sudo, no shell access beyond what the service needs, and no membership in docker or similar groups.
  • Keep the database on a separate host or at least a separate container with a network policy, and give the forge account only the grants it needs on its own schema.
  • Do not co-locate the forge with CI runners on the same host or in the same network namespace. Runner tokens are the most valuable thing a forge compromise yields, and a runner can usually reach everything the pipeline deploys to.
  • Mount a temporary filesystem for the service that is noexec where the product supports it. The Gitea advisory lists a writable and executable temporary filesystem as a precondition for the hook exploit; removing the executable bit from that mount breaks the chain for that specific bug and for similar ones.

Step 3: Control Egress

The observed dropper downloaded an architecture-specific payload from the internet and then ran it. The Habr author's response included blocking outbound internet from the container, which is the correct instinct. A forge needs outbound access to a short list: mirrors it pulls, webhook targets it posts to, an SMTP relay, maybe a package proxy. Allowlist those and deny everything else.

Egress control does not stop a smarter attacker. The published proof-of-concept for the Gitea bug returned command output through a Git branch fetched over authenticated Smart HTTP, using no outbound connection at all. Treat egress filtering as a way to defeat commodity miners and to generate a loud alert when something tries to phone home, not as a data-theft control.

Step 4: Make Secrets Cheap to Rotate

If secrets are painful to rotate, they will not be rotated after an incident, and the incident will quietly continue. Structure the deployment so that the following can be regenerated in under an hour:

  • The application secrets in app.ini (session, JWT, internal token, and similar).
  • The database password.
  • Every OAuth client secret registered with the forge and every OAuth application the forge registers with upstream identity providers.
  • Runner registration tokens and any long-lived personal access tokens used by automation.
  • Deploy keys and SSH keys the forge uses for mirroring.

Keep an inventory of these before you need it. A post-compromise checklist for self-hosted services is only executable if the list already exists.

Step 5: PatchPatch🛡️A software update that fixes security vulnerabilities, bugs, or adds improvements to an existing program. on a Fixed Cadence

Gitea shipped the diffpatch fix in 1.27.1 on July 27, 2026, and then a further batch of seven CVEs in 1.27.2 on August 14, including a second RCE and a token-scope bypass. Both release posts told users to upgrade as soon as possible. The KEV entry and the confirmed attacks arrived a month after the first fix. Small teams that update quarterly are exactly the population that gets hit in the gap.

Subscribe to the vendor's release feed, pin a weekly window to apply patch releases, and treat a security advisory with a CVSS above 9 as a same-day event. A forge is not a low-priority internal tool; it holds the source of everything you deploy and, usually, the credentials to deploy it.

Step 6: Instrument for the Signals That MatterMatter🏠A new universal smart home standard backed by Apple, Google, and Amazon for cross-platform compatibility.

Detection on a forge is easier than most services because normal activity is predictable. Alert on:

  • New user accounts, always. With registration closed this should be an admin action you recognize.
  • New repositories created by accounts less than a day old.
  • Executable files appearing under hooks directories in repository storage outside of provisioning.
  • Sustained CPU above the service's baseline. The Habr case was discovered because the hosting provider complained about 70% CPU.
  • Outbound connection attempts blocked by the egress policy.

Putting It Together

None of these controls is exotic, and none of them replaces patching. Their value is that they change the outcome when patching lags: closed registration means the scanner never gets write access; an isolated, egress-controlled container means a successful exploit yields a miner instead of a runner token; cheap secret rotation means the recovery takes an afternoon instead of a quarter. Run through the list once, write down the state, and re-verify it whenever you upgrade.