bijavix blog

Gitea Server Setup

bijavix <[email protected]>

Install, configure and harden your own self-hosted git server with Gitea. Secure deployment with MySQL database, HTTPS, system integrated SSH server and backups.

You should set up ufw, ssh and unattended-upgrades securely. These steps are almost identical on every Debian server setup, so they are omittede.

1. Install required packages

apt install gnupg git curl jq

2. MySQL/MariaDB Database configuration (Local or remote)

CREATE USER 'gitea'@'giteaserver' IDENTIFIED BY 'GITRS_MYSQL_PASSWORD';
CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_bin';
GRANT ALL PRIVILEGES ON giteadb.* TO 'gitea'@'giteaserver';
FLUSH PRIVILEGES;

3. Download and verify Gitea

export GITEA_VER=$(curl -s https://api.github.com/repos/go-gitea/gitea/releases/latest | grep '"tag_name":' | cut -d'"' -f4 | sed 's/^v//')
wget -O gitea https://dl.gitea.com/gitea/$(GITEA_VER)/gitea-$(GITEA_VER)-linux-amd64
wget -O gitea.asc https://dl.gitea.com/gitea/$(GITEA_VER)/gitea-$(GITEA_VER)-linux-amd64.asc

gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2
gpg --verify gitea.asc gitea

chmod +x gitea

4. Create the git system user and directories

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

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

5. Install Gitea binary and bash-completion

export GITEA_WORK_DIR=/var/lib/gitea/

cp gitea /usr/local/bin/gitea

wget -O gitea_bash_autocomplete https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/autocompletion/bash_autocomplete
cp gitea_bash_autocomplete /usr/share/bash-completion/completions/gitea

6. Create and enable the systemd service

wget -O gitea.service https://raw.githubusercontent.com/go-gitea/gitea/refs/heads/release/$(GITEA_VER)/contrib/systemd/gitea.service
cp gitea.service /etc/systemd/system/gitea.service

nano /etc/systemd/system/gitea.service

# Uncomment CapabilityBoundingSet=CAP_NET_BIND_SERVICE and AmbientCapabilities=CAP_NET_BIND_SERVICE to allow binding to ports <1024.

sudo systemctl enable gitea --now
cd /var/lib/gitea/custom/
gitea cert --host giteaserver --rsa-bits 4096
chown root:git *.pem
chmod 640 *.pem
cd ~

8. Adjust app.ini (auto-generated on first run)

Reference documentation: https://docs.gitea.com/next/administration/config-cheat-sheet This is just a template with the most interesting options, many variables should be already set, some of them with secret tokens that you should’t be modifying.

APP_NAME = My Gitea Server
RUN_USER = git
WORK_PATH = /var/lib/gitea
RUN_MODE = prod

[database]
DB_TYPE   = mysql
HOST      = dbserver:3306
NAME      = giteadb
USER      = gitea
PASSWD    = GITEA_MYSQL_PASSWORD
SCHEMA    =
SSL_MODE  = skip-verify
PATH      = /var/lib/gitea/data/gitea.db
LOG_SQL   = false

[repository]
ROOT = /var/lib/gitea/data/gitea-repositories

[server]
SSH_DOMAIN           = git.example.com
DOMAIN               = git.example.com
ROOT_URL             = https://git.example.com/
PROTOCOL             = https
HTTP_PORT            = 3000
SSH_PORT             = 22
DISABLE_SSH          = false
START_SSH_SERVER     = false
SSL_MODE             = self_signed
CERT_FILE            = cert.pem
KEY_FILE             = key.pem
APP_DATA_PATH        = /var/lib/gitea/data
LANDING_PAGE         = login
LFS_START_SERVER     = true
LFS_ALLOW_PURE_SSH   = true
LFS_JWT_SECRET       = LFS_JWT_SECRET_HERE
OFFLINE_MODE         = true

[lfs]
PATH = /var/lib/gitea/data/lfs

[mailer]
ENABLED = false

[service]
REGISTER_EMAIL_CONFIRM            = false
ENABLE_NOTIFY_MAIL                = false
DISABLE_REGISTRATION              = true
ALLOW_ONLY_EXTERNAL_REGISTRATION  = false
ENABLE_CAPTCHA                    = true
REQUIRE_SIGNIN_VIEW               = true
DEFAULT_KEEP_EMAIL_PRIVATE        = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING       = true
NO_REPLY_ADDRESS                  = noreply.localhost

[openid]
ENABLE_OPENID_SIGNIN = false
ENABLE_OPENID_SIGNUP = false

[cron.update_checker]
ENABLED               = true
ENABLE_SUCCESS_NOTICE = true
SCHEDULE              = @every 24h

[cron.gc_lfs]
ENABLED                      = false
RUN_AT_START                 = false
SCHEDULE                     = @every 72h
OLDER_THAN                   = 168h
LAST_UPDATED_MORE_THAN_AGO   = 72h
NUMBER_TO_CHECK_PER_REPO     = 0
PROPORTION_TO_CHECK_PER_REPO = 1

[session]
PROVIDER = file

[log]
MODE       = console
LEVEL      = info
ROOT_PATH  = /var/lib/gitea/log

[repository.pull-request]
DEFAULT_MERGE_STYLE = merge

[repository.signing]
DEFAULT_TRUST_MODEL = committer

[security]

INSTALL_LOCK         = true
INTERNAL_TOKEN       = SECURITY_TOKEN_HERE
PASSWORD_HASH_ALGO   = scrypt

[oauth2]
JWT_SECRET = OAUTH_JWT_SECRET_HERE

Gitea can share the same SSH port with the system sshd. Delete and re-add SSH keys in Gitea after enabling it. * Use your Debian user for regular system SSH. * Use the git user for Git SSH.

9. Harden permissions

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

Gitea requires manual updating, I recommend downloading the official upgrade script and setting up a cron job with my update check script to get emailed for new versions.

10. Gitea Upgrade script

wget -O giteaUpgrade.sh https://github.com/go-gitea/gitea/blob/main/contrib/upgrade.sh
chmod +x giteaUpgrade.sh
./giteaUpgrade.sh

10.1. Update-Notifier script

#!/bin/bash
set -euo pipefail

# Configuration
MAIL_TO="root@localhost"
GITEA_BINARY="/usr/local/bin/gitea"
VERSION_JSON_URL="https://dl.gitea.com/gitea/version.json"
CONNECT_TIMEOUT="${CONNECT_TIMEOUT:-10}"

# Ensure required commands are available
for cmd in curl jq mail; do
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "Error: '$cmd' is required but not installed." >&2
    exit 1
  fi
done

# Fetch latest version
latest=$(curl --connect-timeout "$CONNECT_TIMEOUT" -sL "$VERSION_JSON_URL" | jq -r .latest.version)
if [[ -z "$latest" ]]; then
  echo "Error: Unable to determine the latest Gitea version." >&2
  exit 1
fi

# Determine current installed version
current=$($GITEA_BINARY --version | awk '{print $3}')
if [[ -z "$current" ]]; then
  echo "Error: Unable to determine the current Gitea version." >&2
  exit 1
fi

# Compare versions and notify if an update is available
if [[ "$current" != "$latest" ]]; then
  subject="Gitea update available: $current to $latest"
  body="A new version of Gitea is available.\n\nCurrent installed version: $current\nLatest available version: $latest\n\nSee: https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md"
  echo -e "$body" | mail -v -s "$subject" "$MAIL_TO"
  echo "Notification sent to $MAIL_TO"
else
  echo "Gitea is up to date (version $current)."
fi

11. Backup and restore

11.1. Create a backup

gitea dump will create a zip file with all the data from your gitea installation, save it in a safe place.

su git -
gitea dump -c /etc/gitea/app.ini

11.2. Restore a backup

su git -
unzip gitea-dump.zip
cd gitea-dump
cp -R app.ini /etc/gitea/conf/app.ini
cp -R custom/* /var/lib/gitea/custom/
cp -R data/* /var/lib/gitea/data/
cp -R repos/* /var/lib/gitea/data/gitea-repositories/
chown -R git:git /etc/gitea/conf/app.ini /var/lib/gitea
rm -rf app.ini custom data repos

11.3. Reset a user password

gitea -c "/etc/gitea/conf/app.ini" admin change-password --username myusername --password newpassword