Gitea is a lightweight, self-hosted Git service written in Go. It offers repositories, pull requests, issues, a package registry and CI through Gitea Actions, while running comfortably on a small server. In this tutorial you will install the official Gitea binary on Ubuntu 24.04, store its data in PostgreSQL, publish the web interface over HTTPS with Nginx and Let's Encrypt, and clone repositories over SSH through the system OpenSSH server.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS (x86_64), for example a CubePath VPS. 1 vCPU and 2 GB of RAM are enough for a small team.
  • A non-root user with sudo privileges.
  • A domain or subdomain, such as git.your_domain, with a DNS A record pointing to your server's public IP. This guide uses your_domain as a placeholder.
  • An SSH key pair on your local computer, to test Git over SSH at the end.

Step 1 - Installing dependencies and opening the firewall

Gitea needs Git on the server, and you will use jq to read the latest version number. Install them together with PostgreSQL and Nginx:

sudo apt update
sudo apt install -y git jq postgresql nginx

Allow SSH, HTTP and HTTPS through UFW. Gitea's own port (3000) stays closed; Nginx will proxy to it:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere

Step 2 - Creating the PostgreSQL database

Open a PostgreSQL shell as the postgres superuser:

sudo -u postgres psql

Create a login role and a UTF-8 database owned by it. Replace your_strong_password with a long random password; you will enter it in the web installer later:

CREATE ROLE gitea WITH LOGIN PASSWORD 'your_strong_password';
CREATE DATABASE gitea WITH OWNER gitea TEMPLATE template0 ENCODING 'UTF8';
\q

Test the connection over TCP, the same way Gitea will connect:

psql -h 127.0.0.1 -U gitea -d gitea -c 'SELECT current_user;'
 current_user
--------------
 gitea
(1 row)

Step 3 - Creating the git user and directories

Gitea runs as a dedicated system user. Naming it git gives you clean SSH clone URLs such as git@your_domain:user/repo.git, because Gitea manages that user's authorized_keys file and the system OpenSSH server handles the connections:

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

Create the working directory for repositories, attachments and logs, and the configuration directory. /etc/gitea is writable by the git group for now so the web installer can save app.ini:

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

Step 4 - Downloading the Gitea binary

Gitea publishes the current stable version in a small JSON file. Read it into a shell variable:

GITEA_VERSION=$(curl -fsSL https://dl.gitea.com/gitea/version.json | jq -r '.latest.version')
echo "$GITEA_VERSION"

The output is a version number such as 1.24.6. If you prefer to pin a version, set GITEA_VERSION by hand from the releases listed at https://dl.gitea.com/gitea/.

Download the binary and its SHA-256 checksum, then verify it:

cd /tmp
curl -fsSLO "https://dl.gitea.com/gitea/${GITEA_VERSION}/gitea-${GITEA_VERSION}-linux-amd64"
curl -fsSLO "https://dl.gitea.com/gitea/${GITEA_VERSION}/gitea-${GITEA_VERSION}-linux-amd64.sha256"
sha256sum -c "gitea-${GITEA_VERSION}-linux-amd64.sha256"
gitea-1.24.6-linux-amd64: OK

Install it to /usr/local/bin and check that it runs:

sudo install -m 755 "gitea-${GITEA_VERSION}-linux-amd64" /usr/local/bin/gitea
gitea --version
Gitea version 1.24.6 built with GNU Make ...

Step 5 - Running Gitea as a systemd service

Create a unit file:

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

Paste the following. It starts Gitea after PostgreSQL, as the git user, with the paths created in Step 3:

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

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

[Install]
WantedBy=multi-user.target

Load the unit, start Gitea and enable it at boot:

sudo systemctl daemon-reload
sudo systemctl enable --now gitea
sudo systemctl status gitea
● gitea.service - Gitea (Git with a cup of tea)
     Loaded: loaded (/etc/systemd/system/gitea.service; enabled; preset: enabled)
     Active: active (running) since ...

Gitea is now listening on port 3000 in installation mode. Confirm it answers locally:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000
200

Step 6 - Configuring Nginx and HTTPS

Create an Nginx site for Gitea:

sudo nano /etc/nginx/sites-available/gitea

Add this server block, replacing your_domain. client_max_body_size controls the largest push or upload that Nginx accepts:

server {
    listen 80;
    listen [::]:80;
    server_name your_domain;

    client_max_body_size 512M;

    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;
    }
}

Enable it, remove the default site and reload Nginx:

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

Install Certbot and request a certificate. The Nginx plugin adds the TLS configuration and an HTTP to HTTPS redirect to this site:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain

Check that renewal will work unattended:

sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/your_domain/fullchain.pem (success)

Step 7 - Completing the web installer

Open https://your_domain in your browser. Gitea shows the Initial Configuration page. Fill in these fields and leave the rest at their defaults:

FieldValue
Database TypePostgreSQL
Host127.0.0.1:5432
Username / Database Namegitea / gitea
Passwordthe password from Step 2
Server Domainyour_domain
SSH Server Port22
Gitea HTTP Listen Port3000
Gitea Base URLhttps://your_domain/

Expand Administrator Account Settings and create your admin user. If you want only the admin to create accounts, open Server and Third-Party Service Settings and tick Disable Self-Registration. Click Install Gitea; after a few seconds you are logged in as the administrator.

The installer has written /etc/gitea/app.ini. Remove write access to the configuration now that it exists:

sudo chmod 750 /etc/gitea
sudo chmod 640 /etc/gitea/app.ini

By default Gitea listens on all interfaces. Bind it to localhost so it is only reachable through Nginx, even if the firewall changes later. Open the file:

sudo nano /etc/gitea/app.ini

In the [server] section add, or edit, this line:

HTTP_ADDR = 127.0.0.1

Restart Gitea:

sudo systemctl restart gitea

Step 8 - Pushing code over HTTPS and SSH

In the web interface, click your avatar, open Settings > SSH / GPG Keys, click Add Key and paste the contents of your local public key (for example ~/.ssh/id_ed25519.pub). Then click + > New Repository, name it hello, and create it.

From your local computer, test SSH authentication:

ssh -T git@your_domain
Hi there, your_user! You've successfully authenticated with the key named laptop, but Gitea does not provide shell access.

Create a local repository and push it:

mkdir hello && cd hello
git init -b main
echo "# hello" > README.md
git add README.md
git commit -m "Initial commit"
git remote add origin git@your_domain:your_user/hello.git
git push -u origin main

Reload the repository page in your browser and the README appears. HTTPS cloning works too with git clone https://your_domain/your_user/hello.git.

Step 9 - Backing up and upgrading Gitea

gitea dump packs the repositories, the database and the configuration into a single zip file. Create a directory owned by the git user and take a first dump:

sudo mkdir -p /var/backups/gitea
sudo chown git:git /var/backups/gitea
sudo chmod 700 /var/backups/gitea
cd /var/backups/gitea
sudo -u git gitea dump -c /etc/gitea/app.ini -w /var/lib/gitea -f "gitea-dump-$(date +%F).zip"
ls -lh /var/backups/gitea

To run it nightly and keep two weeks of dumps, create a cron file:

sudo nano /etc/cron.d/gitea-dump
30 2 * * * git cd /var/backups/gitea && /usr/local/bin/gitea dump -c /etc/gitea/app.ini -w /var/lib/gitea -f "gitea-dump-$(date +\%F).zip" >/dev/null 2>&1
45 3 * * * git find /var/backups/gitea -name 'gitea-dump-*.zip' -mtime +14 -delete

Copy the dumps to another location regularly, such as object storage.

To upgrade, take a dump first, since Gitea migrates the database automatically on start. Then run the GITEA_VERSION, download and sha256sum commands from Step 4 (not the install command) to fetch the new release, and replace the binary while the service is stopped:

sudo systemctl stop gitea
sudo install -m 755 "/tmp/gitea-${GITEA_VERSION}-linux-amd64" /usr/local/bin/gitea
sudo systemctl start gitea

Troubleshooting

  • 502 Bad Gateway: Gitea is not running. Check sudo journalctl -u gitea -n 50. A common cause is app.ini not being readable by the git group after changing permissions.
  • ssh -T asks for a password: the key was not added in Gitea, or the git user's shell is not /bin/bash. Check /home/git/.ssh/authorized_keys contains a line with gitea serv.
  • Push fails with "413 Request Entity Too Large": increase client_max_body_size in the Nginx site and reload Nginx.
  • Links and clone URLs show http:// or port 3000: fix ROOT_URL in the [server] section of app.ini to https://your_domain/ and restart Gitea.

Conclusion

You now run Gitea on Ubuntu 24.04 with PostgreSQL, HTTPS through Nginx, SSH clones through the system OpenSSH server and nightly dumps. Next, you can enable Gitea Actions and register a runner to build your code, configure a mail server in the [mailer] section of app.ini for notifications, or mirror existing GitHub repositories with + > New Migration.