$linuxjunkies
>

Self-Host Git with Gitea or Forgejo

Install Forgejo or Gitea on Linux, configure Caddy as a reverse proxy, enable SSH access on a custom port, push your first repository, and register an Actions runner.

IntermediateUbuntuDebianFedoraArch12 min readUpdated June 7, 2026

Before you start

  • A Linux server with a public IP and a domain or subdomain pointing to it
  • Root or sudo access on the server
  • Ports 80, 443, and 2222 (or your chosen SSH port) reachable from the internet
  • Docker or Podman installed if you plan to use Actions runners with container jobs

Running your own Git forge gives you full control over repositories, CI pipelines, and user access without depending on third-party services. Forgejo is the actively maintained community fork of Gitea — both share the same installation method and configuration format, so the steps below apply to either. This guide installs via the official binary, puts Caddy in front as a reverse proxy, enables SSH passthrough, creates your first repository, and registers an Actions runner.

Choose: Gitea or Forgejo?

Forgejo (forgejo.org) is the recommended choice for new deployments. It is a hard fork of Gitea, developed by the Codeberg team, with a stronger governance policy and fully compatible API. Gitea (gitea.io) remains widely deployed and is equally valid if you already have tooling around it. The binary names and config keys differ only in branding.

Prerequisites and Server Preparation

You need a Linux server (1 vCPU, 1 GB RAM minimum), a domain or subdomain pointing to it, and a working SMTP relay if you want email notifications. Port 80 and 443 must be open, and you should pick a separate port for Git-over-SSH — port 22 works if you move the host SSH daemon, but 2222 is less disruptive.

Create a dedicated system user

sudo adduser \
  --system \
  --shell /bin/bash \
  --gecos 'Git Service' \
  --group \
  --disabled-password \
  --home /home/git \
  git

On Fedora/RHEL/Rocky, replace adduser with useradd -r -m -d /home/git -s /bin/bash git.

Open firewall ports

Choose the tool that matches your distro:

# ufw (Debian/Ubuntu)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 2222/tcp
# firewalld (Fedora/RHEL/Rocky)
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload

Install the Forgejo Binary

Grab the latest stable release from Codeberg releases. Always verify the checksum.

FORGEJO_VERSION="7.0.4"
ARCH="amd64"   # use arm64 for ARM servers

wget -q "https://codeberg.org/forgejo/forgejo/releases/download/v${FORGEJO_VERSION}/forgejo-${FORGEJO_VERSION}-linux-${ARCH}" \
  -O /tmp/forgejo

wget -q "https://codeberg.org/forgejo/forgejo/releases/download/v${FORGEJO_VERSION}/forgejo-${FORGEJO_VERSION}-linux-${ARCH}.sha256" \
  -O /tmp/forgejo.sha256

# Verify
(cd /tmp && sha256sum -c forgejo.sha256)
sudo install -m 755 /tmp/forgejo /usr/local/bin/forgejo

Create required directories

sudo mkdir -p /etc/forgejo /var/lib/forgejo
sudo chown -R git:git /etc/forgejo /var/lib/forgejo
sudo chmod 750 /etc/forgejo

Configure Forgejo

Forgejo generates /etc/forgejo/app.ini during first-run setup, but you can pre-seed critical values to avoid clicking through the web installer. Create the file as the git user:

sudo -u git tee /etc/forgejo/app.ini <<'EOF'
APP_NAME = My Forgejo
RUN_USER = git
WORK_PATH = /var/lib/forgejo

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

[database]
DB_TYPE = sqlite3
PATH    = /var/lib/forgejo/forgejo.db

[security]
INSTALL_LOCK   = false
SECRET_KEY     = changethisrandomstring
INTERNAL_TOKEN = changethisothertoken

[service]
DISABLE_REGISTRATION = false
REQUIRE_SIGNIN_VIEW  = false
EOF

Replace changethis* values with output from openssl rand -hex 32. For production, switch to PostgreSQL by changing the [database] block — SQLite is fine for small teams.

Create the systemd Service

sudo tee /etc/systemd/system/forgejo.service <<'EOF'
[Unit]
Description=Forgejo (Beyond coding. We forge.)
After=network.target

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

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

Check it started cleanly:

sudo systemctl status forgejo

Reverse Proxy with Caddy

Caddy handles TLS automatically via ACME. Install it, then write a Caddyfile.

# Debian/Ubuntu
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy
# Fedora/RHEL/Rocky
sudo dnf install -y 'dnf-command(copr)'
sudo dnf copr enable -y @caddy/caddy
sudo dnf install -y caddy
# Arch
sudo pacman -S caddy
sudo tee /etc/caddy/Caddyfile <<'EOF'
git.example.com {
    reverse_proxy localhost:3000
}
EOF
sudo systemctl enable --now caddy
sudo systemctl reload caddy

Caddy will obtain a Let's Encrypt certificate automatically. Browse to https://git.example.com — if INSTALL_LOCK = false you'll see the web installer; complete it once to lock the config.

SSH Access

Because Forgejo's built-in SSH server listens on port 2222, your users clone with ssh://[email protected]:2222/user/repo.git. If you prefer port 22, move the host SSH daemon to another port first, then set SSH_LISTEN_PORT = 22 in app.ini.

Add your own public key via the Forgejo web UI under Settings → SSH / GPG Keys, then test:

ssh -T -p 2222 [email protected]
# Expected: Hi username! You've successfully authenticated...

Create Your First Repository

  1. Log in, click the + icon → New Repository.
  2. Fill in the name, choose visibility, optionally initialise with a README.
  3. Push an existing local project:
cd ~/myproject
git remote add origin ssh://[email protected]:2222/youruser/myproject.git
git push -u origin main

Register an Actions Runner

Forgejo Actions is compatible with GitHub Actions workflow syntax. Enable it first in app.ini:

sudo -u git tee -a /etc/forgejo/app.ini <<'EOF'

[actions]
ENABLED = true
EOF
sudo systemctl restart forgejo

Install the runner binary on the same host (or a separate machine):

RUNNER_VERSION="3.5.0"
wget -q "https://codeberg.org/forgejo/runner/releases/download/v${RUNNER_VERSION}/forgejo-runner-${RUNNER_VERSION}-linux-amd64" \
  -O /usr/local/bin/forgejo-runner
chmod +x /usr/local/bin/forgejo-runner

Generate a registration token in the Forgejo UI: Site Administration → Runners → Create Registration Token. Then register and start the runner:

forgejo-runner register \
  --instance https://git.example.com \
  --token <your-token> \
  --name my-runner \
  --labels ubuntu-latest:docker://node:20-bookworm
forgejo-runner daemon

Wrap it in a systemd unit using the same pattern as the main service. Runners require Docker or Podman installed on the runner host for container-based jobs.

Verification

# Service health
systemctl is-active forgejo caddy

# Reachability
curl -sI https://git.example.com | head -5

# SSH
ssh -T -p 2222 [email protected]

# API check
curl -s https://git.example.com/api/v1/version

Troubleshooting

  • Port 3000 not responding: Check journalctl -u forgejo -n 50. A wrong WORK_PATH or missing directory ownership is the most common cause.
  • TLS certificate not issued: Confirm DNS is resolving to the server and port 80 is open — Caddy needs HTTP-01 challenge access.
  • SSH key refused: Verify the key is added in the Forgejo UI, and that you're targeting the correct port. Run ssh -vT -p 2222 [email protected] for verbose output.
  • Runner jobs queued but never picked up: Ensure the runner is running (forgejo-runner daemon), that Docker/Podman is available, and that the label on the runner matches the runs-on value in your workflow file.
  • SELinux blocking connections (Fedora/RHEL): Run sudo setsebool -P httpd_can_network_connect 1 if Caddy cannot proxy to localhost:3000.
tested on:Ubuntu 24.04Debian 12Fedora 40Rocky 9

Frequently asked questions

Can I migrate from GitHub or GitLab to Forgejo?
Yes. Forgejo's migration tool (Repository → Migrate) can import repositories, issues, pull requests, milestones, and labels from GitHub, GitLab, Gitea, and several other platforms over the API.
Is SQLite safe for production use?
SQLite works fine for small teams (under ~20 active users) but lacks concurrent write performance. Switch to PostgreSQL for anything larger by updating the [database] block in app.ini and running forgejo migrate.
How do I update Forgejo to a new version?
Download the new binary, verify its checksum, stop the service with systemctl stop forgejo, replace /usr/local/bin/forgejo, then start the service again. Forgejo runs database migrations automatically on startup.
Can Forgejo Actions use the same workflow files as GitHub Actions?
Mostly yes. Forgejo Actions supports the same YAML syntax and most common actions from the GitHub marketplace. Some actions that depend on GitHub-specific APIs will not work, but community-maintained alternatives usually exist.
How do I disable public registration after setup?
Set DISABLE_REGISTRATION = true in the [service] section of app.ini and restart Forgejo. You can also toggle this at runtime under Site Administration → Settings without editing the file.

Related guides