How To Install Gitea on Ubuntu 26.04 LTS

Install Gitea on Ubuntu 26.04

If you’ve spent any real time managing self-hosted developer infrastructure, you already know the pain of choosing between a bloated GitLab instance that eats 4GB of RAM just to idle, and something lighter that still gives your team the collaboration features they expect. That’s the exact gap Gitea fills, and it’s why so many small-to-mid-sized engineering teams — and honestly, a fair number of solo sysadmins running homelabs — keep coming back to it.

Ubuntu 26.04 LTS, codenamed “Resolute Raccoon,” landed in April 2026 with a hardened kernel, Rust-based core utilities, and five years of guaranteed security support running through 2031. That long support window matters a lot when you’re picking an OS for something as central as your Git hosting platform. You don’t want to be forced into an OS migration two years into running production repositories.

This guide walks through installing Gitea on a fresh Ubuntu 26.04 LTS server the way you’d actually do it in production — not the “quick demo” way. That means a dedicated system user, a real database backend instead of SQLite, a reverse proxy with TLS termination, systemd service management, and the firewall rules you’d want before exposing this to the internet. Along the way, there are notes on why certain choices matter, because copying commands blindly is how servers end up misconfigured six months down the road.

Whether you’re setting this up as a private GitHub alternative for a five-person dev team, a CI/CD backend paired with Gitea Actions, or just a personal code vault you control end-to-end, the fundamentals below apply. There are a few gotchas specific to 26.04’s toolchain changes worth knowing about too, particularly around the new default utilities and permission handling.

What You Need Before Starting

A minimal Gitea instance runs comfortably on 1 vCPU and 512MB RAM for small teams, but if you’re planning to run Gitea Actions (the built-in CI/CD runner) or expect more than a handful of concurrent users, budget at least 2 vCPUs and 2GB RAM. Disk I/O matters more than raw CPU for Git hosting — repository operations are disk-heavy, so SSD-backed storage isn’t optional if you’re serving anything beyond a toy project.

Here’s what you should have ready:

  • A clean Ubuntu 26.04 LTS server (VPS or bare metal) with root or sudo access
  • A non-root user with sudo privileges — never run production services as root
  • A domain name or subdomain pointed to your server’s IP (for TLS later)
  • Basic firewall access (ufw or iptables) and open ports 22, 80, 443
  • Around 2GB RAM minimum for anything beyond personal use

Step 1: Update the System and Install Dependencies

Start clean. Skipping this step is how you end up chasing dependency conflicts three steps later.

sudo apt update && sudo apt full-upgrade -y
sudo apt install -y wget curl git vim gnupg2 ca-certificates apt-transport-https

Ubuntu 26.04 ships with Rust-based coreutils and sudo-rs by default, replacing the traditional GNU implementations. In practice this shouldn’t break anything for Gitea’s install process — the command syntax is unchanged — but if you’re running older automation scripts that parse specific GNU-only flags or error message formats, test them first. It’s a subtle change that’s bitten a few admins during early adoption.

Reboot if the kernel was updated:

sudo reboot

Step 2: Create a Dedicated System User for Gitea

Running Gitea under a dedicated, low-privilege user is non-negotiable from a security standpoint. If the application is ever compromised through a vulnerability, you don’t want that foothold inheriting broader system access.

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

This creates a system account named git with no login password, a proper home directory, and its own group. It’s the same pattern you’d use for any service account — Nginx workers, database daemons, you name it.

Step 3: Install and Configure PostgreSQL

SQLite works for testing, but it’s a bottleneck under any real concurrent load — Git operations plus web UI requests plus API calls from CI pipelines will eventually cause write-lock contention. PostgreSQL is the backend most production Gitea deployments settle on, and Gitea officially supports PostgreSQL 12 and newer.

sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresql

Create the database and a dedicated role:

sudo -u postgres psql -c "CREATE ROLE gitea WITH LOGIN PASSWORD 'ChangeThisPassword';"
sudo -u postgres psql -c "CREATE DATABASE giteadb OWNER gitea;"

Use a genuinely strong, randomly generated password here — this isn’t the place to reuse credentials from another service. A leaked database password on a Git server means someone potentially reading every private repo you host.

Step 4: Download and Install the Gitea Binary

Gitea distributes as a single statically linked binary, which is one of its biggest operational advantages over GitLab’s much heavier installation footprint. As of mid-2026, the current stable branch is the 1.26.x series, with 1.26.4 shipping important security fixes and a hotfix for a repository code-page regression.

Check the latest release before downloading, since version numbers shift:

wget -O gitea https://dl.gitea.com/gitea/1.26.4/gitea-1.26.4-linux-amd64
chmod +x gitea
sudo mv gitea /usr/local/bin/gitea

Verify it runs:

gitea --version

If you’re on ARM hardware (increasingly common with cloud instances these days), grab the linux-arm64 build instead — the binary naming convention makes this an easy swap.

Step 5: Create Required Directories and Set Permissions

Gitea needs a structured directory layout for its data, config, logs, and repositories. Getting the ownership wrong here is one of the most common install-time mistakes — it either causes silent permission errors during setup or, worse, lets Gitea run with overly permissive directory access.

sudo mkdir -p /var/lib/gitea/{custom,data,log}
sudo mkdir -p /etc/gitea
sudo chown -R git:git /var/lib/gitea/
sudo chmod -R 750 /var/lib/gitea/
sudo chown root:git /etc/gitea
sudo chmod 770 /etc/gitea

Note the 750 permission on the data directories — this restricts access to the owner and group only, which matters because repository data and SSH keys live here.

Step 6: Configure Gitea as a systemd Service

This is where Gitea becomes a proper managed service rather than something you run manually in a tmux session and forget about (a mistake that’s more common than it should be).

Create the service file:

sudo vim /etc/systemd/system/gitea.service

Paste the following configuration:

[Unit]
Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target
Wants=postgresql.service
After=postgresql.service

[Service]
RestartSec=3s
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/gitea/
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea

[Install]
WantedBy=multi-user.target

The Wants=postgresql.service and After=postgresql.service directives ensure Gitea doesn’t try to start before the database is ready — a race condition that’s easy to overlook and painful to debug when it happens on boot.

Reload systemd and enable the service, but don’t start it yet:

sudo systemctl daemon-reload
sudo systemctl enable gitea

Step 7: Run the Web-Based Installer

Start Gitea for the first time:

sudo systemctl start gitea
sudo systemctl status gitea

By default, Gitea listens on port 3000. Before exposing this publicly, access it temporarily via SSH tunnel or a temporary firewall rule scoped to your IP:

ssh -L 3000:localhost:3000 youruser@your-server-ip

Then open http://localhost:3000 in your browser. The setup wizard will ask for:

  • Database type (select PostgreSQL), host (127.0.0.1:5432), username, password, and database name
  • Site title, repository root path, and the git user
  • Server domain and base URL (use your real domain here, even before TLS is configured)
  • SMTP settings for email notifications (optional but recommended for password resets)
  • Admin account creation — do this now rather than relying on the first-registered-user-becomes-admin behavior

Once submitted, Gitea writes its configuration to /etc/gitea/app.ini. This file becomes your primary tuning surface going forward.

Install Gitea on Ubuntu 26.04

Step 8: Reverse Proxy with Nginx and TLS

Running Gitea directly on port 3000 exposed to the internet is asking for trouble — no TLS termination, no rate limiting, no clean way to run other services on the same host. Nginx as a reverse proxy solves all three.

sudo apt install -y nginx
sudo vim /etc/nginx/sites-available/gitea
server {
    listen 80;
    server_name git.yourdomain.com;

    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;
        client_max_body_size 512M;
    }
}

That client_max_body_size directive is easy to forget and it will absolutely bite you the first time someone tries to push a repo with large binary assets or LFS objects — Nginx’s default 1MB limit will silently reject the push with a 413 error that has nothing obviously to do with body size unless you know to look for it.

Enable the site and reload:

sudo ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Now layer on TLS with Certbot:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d git.yourdomain.com

Certbot handles certificate issuance and auto-configures the HTTPS server block. Set up the renewal timer check (usually already active by default):

sudo systemctl status certbot.timer

Step 9: Enable Git Over SSH

Most teams prefer pushing over SSH rather than HTTPS, mainly because it avoids embedding credentials in remote URLs. Gitea’s built-in SSH server runs on port 22 by default, which conflicts with the host’s own SSH daemon. You have two clean options: run Gitea’s SSH on an alternate port (2222 is conventional), or let Gitea use the built-in SSH server exclusively and move the OS SSH daemon elsewhere.

Edit /etc/gitea/app.ini:

[server]
SSH_PORT = 2222
SSH_LISTEN_PORT = 2222

Restart Gitea and open the port:

sudo systemctl restart gitea
sudo ufw allow 2222/tcp

Users will then clone with a slightly modified syntax: ssh://git@git.yourdomain.com:2222/username/repo.git

Firewall and Security Hardening

Ubuntu 26.04 ships with a refreshed Security Center app and improved OpenSSH defaults, including post-quantum key exchange support baked in. That’s a nice baseline, but it doesn’t replace basic hardening discipline.

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 2222/tcp
sudo ufw enable

A few practices worth adopting from day one:

  • Disable the Gitea “Enable Registration” option in app.ini unless you genuinely want public sign-ups — otherwise you’ll be fighting spam accounts within a week
  • Enforce 2FA for admin accounts through Gitea’s user settings
  • Run fail2ban with a Gitea-specific jail watching failed login attempts in the log directory
  • Keep the database bound to localhost only — never expose port 5432 externally
  • Rotate the SECRET_KEY and INTERNAL_TOKEN values in app.ini only during initial setup, never after repos are in use, since it breaks existing sessions and tokens

Performance Tuning for Production Workloads

Gitea is lightweight by design, but a few tuning adjustments matter once real traffic hits the server.

Database connection pooling. Under [database] in app.ini, set MAX_OPEN_CONNS and MAX_IDLE_CONNS appropriately for your expected concurrency — the defaults are conservative and will throttle throughput on busier instances.

Disk I/O. Git operations are I/O-bound, not CPU-bound. If you’re on cloud storage, provisioned IOPS volumes noticeably reduce clone and push latency compared to burstable/gp2-tier storage, especially once repositories grow past a few hundred MB.

LFS storage offload. For teams pushing large binary assets, configure Gitea’s Git LFS support to point at object storage (S3-compatible) rather than local disk. It keeps the primary volume lean and avoids disk pressure during backup windows.

Caching. Enable Gitea’s built-in cache using Redis instead of the default memory cache once you’re running multiple app instances or want cache persistence across restarts:

[cache]
ADAPTER = redis
HOST = redis://127.0.0.1:6379/0

Log rotation. Gitea’s logs grow fast under heavy Actions usage. Set up logrotate explicitly rather than relying on defaults — an unbounded log file has quietly filled more than one root partition over the years.

Troubleshooting Common Issues

  • Gitea service fails to start with a permission error. Almost always a directory ownership mismatch. Re-run the chown -R git:git /var/lib/gitea/ command and check that /etc/gitea/app.ini is readable by the git group.
  • 502 Bad Gateway from Nginx. This usually means Gitea isn’t actually listening on port 3000 yet — check systemctl status gitea and journalctl -u gitea -n 50 before assuming Nginx config is at fault.
  • Large file pushes fail with HTTP 413. As mentioned above, this is Nginx’s client_max_body_size limit, not a Gitea configuration issue. Increase it in the server block.
  • SSH clone fails with “Permission denied (publickey)”. Confirm the user’s SSH key is registered under their Gitea profile settings, and double-check the port number matches your SSH_PORT configuration if you moved it off 22.
  • Database connection refused during setup wizard. Verify PostgreSQL is listening on the expected socket and that pg_hba.conf allows local connections with password authentication (md5 or scram-sha-256), not just peer auth.
  • Slow repository browsing on large repos. Enable Gitea’s built-in Git blame/history caching and consider increasing LFS_JWT_SECRET timeout values if large repos are timing out during clone operations under load.
r00t is a Linux Systems Administrator and open-source advocate with over ten years of hands-on experience in server infrastructure, system hardening, and performance tuning. Having worked across distributions such as Debian, Arch, RHEL, and Ubuntu, he brings real-world depth to every article published on this blog. r00t writes to bridge the gap between complex sysadmin concepts and practical, everyday application — whether you are configuring your first server or optimizing a production environment. Based in New York, US, he is a firm believer that knowledge, like open-source software, is best when shared freely.

Related Posts