Как установить и настроить Gitea на VPS
Gitea — это свой GitHub на своём сервере: один Go-бинарник, около 180 МБ памяти в покое и веб-интерфейс, который открывается быстрее, чем страница репозитория на GitLab. Сама установка Gitea занимает минут двадцать, но спотыкаются обычно не на ней, а на том, что идёт следом: конфиг, реверс-прокси, SSH и кнопка «Clone», которая упорно отдаёт localhost:3000. Пройдём весь путь от чистой Ubuntu 24.04 до рабочего git push по HTTPS и по SSH.
Содержание
- Что такое Gitea и что подготовить до установки
- Установка Gitea из бинарника: пользователь, каталоги, systemd
- app.ini: конфиг, который решает всё
- Nginx, HTTPS и ROOT_URL: чтобы клон работал снаружи
- SSH к репозиториям: порт 22, ключи и вариант в Docker
- После установки: бэкап, обновление и безопасность
- Какой сервер под Gitea взять в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что такое Gitea и что подготовить до установки
Gitea — форк Gogs, написанный на Go и собранный в один исполняемый файл. Ни Ruby, ни Node, ни Redis с Sidekiq: только бинарник, база данных и каталог с репозиториями. Отсюда и аппетиты — ls -lh /usr/local/bin/gitea покажет около 130 МБ на диске, а рабочий процесс в покое держит 160–200 МБ RSS. Плата за лёгкость честная: нет встроенного сканирования контейнеров, нет сложных пайплайнов из коробки и меньше корпоративной обвязки — подробный разбор в материале Gitea против GitLab.
Что нужно иметь до первой команды:
- Домен и A-запись. Проверьте до установки:
dig +short git.example.comдолжен вернуть IP вашего сервера. Без этого не выпустится сертификат, а без сертификата не заработает половина конфига. - Открытые порты. Наружу — только 22, 80 и 443. Порт 3000 остаётся локальным, Gitea слушает
127.0.0.1, снаружи с ней говорит Nginx.
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
- Пакеты.
apt update && apt install -y git git-lfs nginx postgresql. В Ubuntu 24.04 приезжают git 2.43 и PostgreSQL 16 — обе версии Gitea устраивают. Требование по Git — не ниже 2.0, но на старых 1.8 из CentOS 7 отвалится частичное клонирование.
Дальше — выбор базы, и он определяет, как сервер поведёт себя через год.
| База | Кому подходит | Что учесть |
|---|---|---|
| SQLite3 | 1–5 человек, до пары десятков репозиториев | Ноль обслуживания, файл gitea.db рядом с данными. При параллельных пушах и запуске CLI-команд на живом сервисе ловите database is locked |
| PostgreSQL 16/17 | Команда, CI, Actions, зеркала | Рекомендуемый вариант. Просит сверху 120–150 МБ RAM, зато переживает нагрузку и нормально бэкапится |
| MySQL 8 / MariaDB | Если СУБД уже стоит на сервере | Только utf8mb4, иначе миграции упадут на эмодзи в описании репозитория |
Ниже — вариант с PostgreSQL. Для SQLite достаточно заменить один блок в конфиге, остальное совпадает.
Установка Gitea из бинарника: пользователь, каталоги, systemd
Официальных пакетов для apt у проекта нет — ставится бинарник. Скачиваем его вместе с подписью и проверяем: релизы подписаны ключом 7C9E68152594688862D62AF62D9AE806EC1592E2.
GITEA=1.25.2
wget -O /tmp/gitea https://dl.gitea.com/gitea/${GITEA}/gitea-${GITEA}-linux-amd64
wget -O /tmp/gitea.asc https://dl.gitea.com/gitea/${GITEA}/gitea-${GITEA}-linux-amd64.asc
gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2
gpg --verify /tmp/gitea.asc /tmp/gitea
install -m 755 /tmp/gitea /usr/local/bin/gitea
В выводе gpg --verify должно быть Good signature from "Teabot <teabot@gitea.io>". Строка BAD signature означает битую или подменённую загрузку — файл не запускать. Проверяем, что бинарник живой:
$ gitea --version
Gitea version 1.25.2 built with GNU Make 4.3, go1.24.6 : bindata, timetzdata, sqlite, sqlite_unlock_notify
Флаг sqlite в списке — тот самый признак, что сборка умеет SQLite. Собранный самостоятельно без тега sqlite бинарник на старте выдаст Unknown database type: sqlite3.
Теперь пользователь и каталоги. Обратите внимание на --shell /bin/bash: с nologin веб-интерфейс заработает, а клонирование по SSH — нет.
adduser --system --group --disabled-password --shell /bin/bash --gecos 'Gitea' --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
База:
sudo -u postgres psql -c "CREATE ROLE gitea WITH LOGIN PASSWORD 'длинный-пароль-сюда';"
sudo -u postgres psql -c "CREATE DATABASE giteadb WITH OWNER gitea TEMPLATE template0 ENCODING 'UTF8';"
Юнит /etc/systemd/system/gitea.service. Строка After= важна: без неё после перезагрузки Gitea стартует раньше PostgreSQL и десять раз подряд не достучится до базы.
[Unit]
Description=Gitea
After=network.target 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
systemctl daemon-reload
systemctl enable --now gitea
journalctl -u gitea -n 30 --no-pager
Два типовых сообщения на этом шаге. Первое означает, что до Gitea дело дошло, а до базы нет:
2026/08/28 12:41:07 routers/common/db.go:36:InitDBEngine() [E] ORM engine initialization attempt #1/10 failed.
Error: pq: password authentication failed for user "gitea"
Лечится паролем в конфиге или строкой в pg_hba.conf, но не переустановкой Gitea. Второе — попытка запустить бинарник от root при RUN_USER = git: Expect user 'git' but current user is: root. Все CLI-команды выполняйте через sudo -u git.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть Giteaapp.ini: конфиг, который решает всё
При первом запуске Gitea поднимает мастер установки на /install. Пользоваться им на публичном сервере не стоит: пока INSTALL_LOCK не выставлен, мастер доступен всем, и первый же зашедший может назначить админом себя и указать свою базу. Правильный порядок — написать /etc/gitea/app.ini руками и закрыть установку сразу.
Секреты генерирует сам Gitea:
sudo -u git gitea generate secret SECRET_KEY
sudo -u git gitea generate secret INTERNAL_TOKEN
Рабочий минимум конфига:
APP_NAME = Git
RUN_USER = git
RUN_MODE = prod
WORK_PATH = /var/lib/gitea
[server]
PROTOCOL = http
DOMAIN = git.example.com
ROOT_URL = https://git.example.com/
HTTP_ADDR = 127.0.0.1
HTTP_PORT = 3000
APP_DATA_PATH = /var/lib/gitea/data
SSH_DOMAIN = git.example.com
START_SSH_SERVER = false
SSH_PORT = 22
LFS_START_SERVER = true
OFFLINE_MODE = true
[database]
DB_TYPE = postgres
HOST = 127.0.0.1:5432
NAME = giteadb
USER = gitea
PASSWD = `длинный-пароль-сюда`
SSL_MODE = disable
[security]
INSTALL_LOCK = true
SECRET_KEY = <из generate secret>
INTERNAL_TOKEN = <из generate secret>
PASSWORD_HASH_ALGO = argon2
[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = true
[repository]
DEFAULT_BRANCH = main
ROOT = /var/lib/gitea/data/gitea-repositories
[session]
COOKIE_SECURE = true
[log]
MODE = console, file
LEVEL = info
ROOT_PATH = /var/lib/gitea/log
Три детали, на которых теряют вечер:
- Пароль базы — в обратных кавычках.
PASSWD = \p@ss#word\`парсится целиком, а без кавычек#начнёт комментарий, и Gitea получит обрезанный пароль. Симптом тот жеpassword authentication failed`, хотя пароль «правильный». OFFLINE_MODE = trueзаставляет отдавать аватары и библиотеки со своего домена, а не с внешних CDN. Для сервера, к которому ходят из России, это заметная разница в скорости открытия страниц.ROOT_URL— с протоколом и слэшем на конце. Именно из него собираются ссылки для клонирования, письма и редиректы после логина.
Закрываем права и заводим администратора:
chown root:git /etc/gitea/app.ini && chmod 640 /etc/gitea/app.ini && chmod 750 /etc/gitea
systemctl restart gitea
sudo -u git GITEA_WORK_DIR=/var/lib/gitea /usr/local/bin/gitea admin user create \
--admin --username maat --email admin@example.com \
--random-password --must-change-password=false --config /etc/gitea/app.ini
В ответ придёт generated random password is 'xxxxxxxxxxxx' — это единственный раз, когда пароль показывают. Без --must-change-password=false Gitea потребует сменить его при первом входе, что удобно, если аккаунт заводите не себе. На SQLite команду выполняйте после systemctl stop gitea, иначе получите database is locked.
Nginx, HTTPS и ROOT_URL: чтобы клон работал снаружи
Gitea умеет TLS сама, но перед ней почти всегда ставят Nginx — ради других сайтов на том же адресе, логов и лимитов. Конфиг /etc/nginx/sites-available/gitea:
server {
listen 443 ssl;
http2 on;
server_name git.example.com;
ssl_certificate /etc/letsencrypt/live/git.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git.example.com/privkey.pem;
client_max_body_size 1024M;
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;
proxy_request_buffering off;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
Сертификат — apt install -y certbot python3-certbot-nginx && certbot --nginx -d git.example.com, подробности в материале про SSL Let's Encrypt на VPS.
Четыре места, где этот шаг ломается:
nginx: [emerg] unknown directive "http2". Отдельная директиваhttp2 on;появилась в nginx 1.25.1, а в Ubuntu 24.04 приезжает 1.24.0 (nginx -vподтвердит). На нём пишите по-старому:listen 443 ssl http2;.- 413 на первом же большом пуше. Дефолтный
client_max_body_size— 1 МБ, и репозиторий с историей в него не влезает:
Enumerating objects: 48213, done.
error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413
send-pack: unexpected disconnect while reading sideband packet
fatal: the remote end hung up unexpectedly
Лечится строкой client_max_body_size, а не советом «увеличьте http.postBuffer» — этот параметр на стороне клиента и на лимит Nginx не влияет.
proxy_request_buffering off;— без него Nginx сначала складывает весь пуш во временный файл, и большой push упирается в диск и в таймаут. С выключенной буферизацией данные идут в Gitea потоком.- Расхождение
ROOT_URLи реального адреса. Оставилиhttp://localhost:3000/— и кнопка «Clone» отдаёт неработающий адрес, письма о задачах ведут в никуда, а приPROTOCOL = httpв ROOT_URL и HTTPS снаружи форма входа отвечаетBad Request: invalid CSRF token. ПравьтеROOT_URLи перезапускайте сервис.
Проверка целиком: curl -I https://git.example.com/api/v1/version должен вернуть 200 OK, а git clone https://git.example.com/maat/test.git — отработать без ошибок. Общая настройка прокси разобрана в статье про Nginx как reverse proxy.
SSH к репозиториям: порт 22, ключи и вариант в Docker
Схема по умолчанию (START_SSH_SERVER = false) — проброс через системный sshd. Ключи, добавленные в веб-интерфейсе, Gitea дописывает в /home/git/.ssh/authorized_keys в таком виде:
command="/usr/local/bin/gitea --config=/etc/gitea/app.ini serv key-3",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3Nz... user@laptop
Проверка после добавления ключа:
$ ssh -T git@git.example.com
Hi there, maat! You've successfully authenticated with the key named laptop, but Gitea does not provide shell access.
Если вместо этого приходит git@git.example.com: Permission denied (publickey), причины по частоте: у пользователя git шелл /usr/sbin/nologin; сбиты права (chmod 700 /home/git/.ssh, chmod 600 /home/git/.ssh/authorized_keys, владелец git:git); в sshd_config есть AllowUsers без git; файл ключей не создавался вообще. Последнее чинится командой sudo -u git gitea admin regenerate keys -c /etc/gitea/app.ini — она перезаписывает authorized_keys по данным из базы.
Если sshd висит не на 22, пропишите тот же номер в SSH_PORT, иначе Gitea будет показывать в интерфейсе адрес, по которому никто не отвечает.
Вариант в Docker удобнее, когда на сервере уже есть compose-хозяйство. Здесь SSH не пробрасывается в системный sshd — контейнер поднимает свой:
services:
server:
image: gitea/gitea:1.25
container_name: gitea
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=giteadb
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=длинный-пароль-сюда
- GITEA__server__ROOT_URL=https://git.example.com/
- GITEA__server__SSH_PORT=2222
- GITEA__server__SSH_LISTEN_PORT=22
volumes:
- ./gitea:/data
- /etc/localtime:/etc/localtime:ro
ports:
- "127.0.0.1:3000:3000"
- "2222:22"
depends_on: [db]
db:
image: postgres:17-alpine
restart: always
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=длинный-пароль-сюда
- POSTGRES_DB=giteadb
volumes:
- ./postgres:/var/lib/postgresql/data
Двойное подчёркивание в GITEA__server__SSH_PORT — механизм переопределения app.ini переменными окружения: секция, ключ. Разница между двумя параметрами принципиальна: SSH_LISTEN_PORT — порт внутри контейнера, SSH_PORT — порт, который Gitea пишет в ссылки для клонирования. Перепутали — интерфейс покажет ssh://git@git.example.com/maat/repo.git вместо ssh://git@git.example.com:2222/maat/repo.git, и клон встанет в таймаут. Не забудьте ufw allow 2222/tcp.
Честный минус контейнерного варианта: бэкапить придётся два каталога (./gitea и ./postgres), а CLI-команды выполнять через docker exec -u git gitea gitea ....
После установки: бэкап, обновление и безопасность
Закройте регистрацию. DISABLE_REGISTRATION = true в конфиге выше — не паранойя: открытый Gitea за неделю обрастает спам-аккаунтами с репозиториями-ссылками. Если регистрация нужна, включайте [service] ENABLE_CAPTCHA = true и EMAIL_DOMAIN_ALLOWLIST с доменом компании.
Fail2ban. Gitea пишет неудачные входы в свой лог, фильтр /etc/fail2ban/filter.d/gitea.conf:
[Definition]
failregex = .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>
ignoreregex =
Джейл смотрит на /var/lib/gitea/log/gitea.log с port = http,https,ssh. Здесь есть тонкость: при MODE = console под systemd логи уходят только в journald и файла не существует — fail2ban молча ничего не банит. Поэтому в конфиге выше стоит MODE = console, file. Подробнее — в материале про настройку fail2ban.
Бэкап. Штатная команда упаковывает базу, репозитории, LFS, вложения и конфиг в один архив:
sudo -u git GITEA_WORK_DIR=/var/lib/gitea /usr/local/bin/gitea dump \
-c /etc/gitea/app.ini --type tar.zst -t /var/backups/gitea-tmp \
-f /var/backups/gitea/gitea-dump-$(date +%F).tar.zst
Флаг -t здесь ключевой. По умолчанию Gitea собирает архив во временном каталоге, и на VPS, где /tmp — это tmpfs в оперативке, дамп репозиториев на 8 ГБ заканчивается no space left on device и убитым по памяти процессом. Указывайте временный каталог на диске и держите свободным объём, равный размеру репозиториев. Честно про восстановление: автоматического «restore» у Gitea нет — архив распаковывают вручную, отдельно заливают дамп базы, отдельно кладут на место gitea-repositories.
Обновление. Порядок: systemctl stop gitea → дамп → подмена бинарника → systemctl start gitea → journalctl -u gitea -f. Миграции применяются автоматически при первом старте новой версии. Два правила без исключений: обратной миграции нет, откатиться на предыдущую версию после запуска не получится; и ветки перепрыгивать нельзя — путь с 1.21 на 1.25 идёт через 1.22, 1.23 и 1.24 по очереди. После обновления полезно прогнать sudo -u git gitea doctor check --all -c /etc/gitea/app.ini — он находит осиротевшие записи и битые хуки.
Про Actions. Встроенный CI в Gitea включается строкой [actions] ENABLED = true, но сам по себе он ничего не выполняет: нужен отдельный процесс act_runner, зарегистрированный по токену. Ставить его на тот же маленький сервер — верный способ уронить Gitea сборкой; почему раннер молчит и как это чинить, разобрано в статье Gitea Actions не запускаются. Остальные грабли эксплуатации собраны в материале частые ошибки Gitea на сервере.
Какой сервер под Gitea взять в MAATRIX
Всё описанное выше делать руками не обязательно. Gitea есть в каталоге приложений apps.maatrix.io: при заказе сервера она разворачивается автоматически — бинарник, база, systemd-юнит и Nginx уже настроены, вставлять команды не нужно. Автоустановка работает на Ubuntu и Debian; логин, пароль и адрес панели появляются в личном кабинете, в разделе «Доступ». На AlmaLinux и других системах установка ручная — по шагам из этой статьи.
Минимум — 1 vCPU, 2 ГБ RAM, 40 ГБ NVMe. Команда до пяти человек, 20–40 репозиториев, без CI. Сам Gitea держит 160–200 МБ, PostgreSQL добавляет 120–150 МБ, остальное — запас на пиковые операции. Честно про 1 ГБ: Gitea там запустится и будет выглядеть живой, но git clone большого репозитория порождает процесс git upload-pack, который спокойно съедает 300–500 МБ, а ночной git gc — ещё больше. Итог предсказуемый: Killed process (git) total-vm:... в dmesg и пятисотая в браузере. Если ставите на 1 ГБ, добавьте swap-файл на 2 ГБ.
Комфортный вариант — 2 vCPU, 4 ГБ RAM, 80 ГБ NVMe. Десять–двадцать разработчиков, зеркала внешних репозиториев, включённый индексатор кода и реестр пакетов. Второе ядро нужно не веб-интерфейсу, а параллельным git pack при пуше нескольких человек одновременно. Диск считайте так: размер репозиториев умножить на два (упаковка и временные файлы) плюс место под LFS и локальные дампы. Расчёт по памяти под конкретную команду — в материале сколько RAM нужно для Gitea.
С собственным раннером Actions — 4 vCPU, 8 ГБ RAM, 160 ГБ NVMe или, что правильнее, отдельная машина под сборки: тогда упавший тест не роняет git-хостинг всей команды.
Локация — Лондон. Причина практическая: сервер постоянно ходит наружу — зеркалит репозитории с GitHub, тянет образы с Docker Hub и ghcr.io для раннера, ставит пакеты с npm и PyPI. С британского адреса всё это работает без зеркал и обходных путей, пинг из Москвы — 45–60 мс, из континентальной Европы — 10–20 мс, для веб-интерфейса и пушей неотличимо от локального. Франция равноценна и удобнее, если команда сидит в ЕС и вы смотрите на GDPR. Россия — вариант, когда в репозиториях лежат персональные данные и вы обязаны хранить их в РФ по 152-ФЗ; закладывайте, что часть внешних реестров придётся проксировать. США берут, если раннеры обращаются к американским API.
Оплата — картами российских банков, по СБП, криптовалютой или токеном MAAT: иностранная карта не нужна, даже когда сервер стоит в Лондоне.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть GiteaОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
Можно ли поставить Gitea туда, где уже работает сайт на Nginx?
Да, это штатный сценарий: Gitea слушает 127.0.0.1:3000, а вы добавляете отдельный server-блок на поддомен git.example.com. Конфликт возникает только по SSH — если 22-й порт уже занят чем-то своим, поднимайте Gitea в Docker с пробросом 2222:22.
Забыл пароль администратора, в интерфейс не войти. Что делать?
Пароль меняется из консоли: sudo -u git gitea admin user change-password --username maat --password 'новый-пароль' -c /etc/gitea/app.ini. Если админа нет вовсе, создайте нового командой gitea admin user create --admin — она работает и при INSTALL_LOCK = true.
Как перенести репозитории с GitHub?
В интерфейсе есть «New Migration»: указываете URL и personal access token, Gitea тянет код, ветки, теги, а с токеном — ещё задачи, пул-реквесты и релизы. Для десятков репозиториев удобнее миграция через API, а для постоянной синхронизации — режим зеркала с интервалом обновления.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.