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
sudoprivileges. - A domain or subdomain, such as
git.your_domain, with a DNS A record pointing to your server's public IP. This guide usesyour_domainas 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:
| Field | Value |
|---|---|
| Database Type | PostgreSQL |
| Host | 127.0.0.1:5432 |
| Username / Database Name | gitea / gitea |
| Password | the password from Step 2 |
| Server Domain | your_domain |
| SSH Server Port | 22 |
| Gitea HTTP Listen Port | 3000 |
| Gitea Base URL | https://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 isapp.ininot being readable by thegitgroup after changing permissions. ssh -Tasks for a password: the key was not added in Gitea, or thegituser's shell is not/bin/bash. Check/home/git/.ssh/authorized_keyscontains a line withgitea serv.- Push fails with "413 Request Entity Too Large": increase
client_max_body_sizein the Nginx site and reload Nginx. - Links and clone URLs show
http://or port 3000: fixROOT_URLin the[server]section ofapp.initohttps://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.
