MAATRIX / Блог / Gitea на сервере: частые ошибки и решения

Gitea на сервере: частые ошибки и решения

Gitea на сервере: частые ошибки и решения

MAATRIX

Gitea ставится за пятнадцать минут и работает годами — но между этими состояниями есть неделя, когда кнопка Clone отдаёт localhost:3000, push по SSH упирается в Permission denied (publickey), а по HTTPS — в HTTP 413. Разберём типовые поломки по схеме «симптом — причина — команда».

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Триаж: четыре слоя и три места, где лежит правда

Больше всего времени теряется на починке не того слоя. Их четыре: процесс, обратный прокси, SSH-транспорт и база. Сначала — три источника правды.

  • Конфиг. При установке из бинарника — /etc/gitea/app.ini, рабочий каталог /var/lib/gitea, репозитории в /var/lib/gitea/data/gitea-repositories; в Docker — /data/gitea/conf/app.ini. Путь у живого процесса: sudo tr '\0' ' ' < /proc/$(systemctl show -p MainPID --value gitea)/cmdline.
  • Логи. При MODE = console в секции [log] (значение по умолчанию) файла gitea.log не существует: всё уходит в journald — journalctl -u gitea -n 200 --no-pager.

Самодиагност проверит права, хуки и схему базы:

sudo -u git /usr/local/bin/gitea doctor check --all \
  --config /etc/gitea/app.ini --work-path /var/lib/gitea

С --fix часть находок чинится сама. Главная ловушка — запускать это от root: сервис откажется стартовать.

Gitea is not supposed to be run as root. Sorry. If you need to use privileged
TCP ports please instead use setcap and the `cap_net_bind_service` capability

Хуже, когда sudo gitea doctor без -u git проходит: в /var/lib/gitea появляются файлы root, и через день Gitea отдаёт 500 Internal Server Error на загрузке аватара. Лечение — chown -R git:git /var/lib/gitea.

Клон-ссылки с localhost, 502 и «invalid CSRF token»

У трёх симптомов один корень: [server] не знает, под каким адресом Gitea видят снаружи.

Clone отдаёт http://localhost:3000/team/app.git или голый IP: ROOT_URL остался от установщика. Он же ломает ссылки в письмах и OAuth-редиректы.

Вход отдаёт Bad Request: invalid CSRF token. С 1.22 Gitea выводит COOKIE_SECURE из протокола в ROOT_URL: при https:// cookie помечается secure и по http://IP:3000 не отправляется — форма логина уходит без токена.

502 Bad Gateway. Смотрите /var/log/nginx/error.log:

[error] 812#812: *17 connect() failed (111: Connection refused) while connecting
to upstream, client: 203.0.113.7, upstream: "http://127.0.0.1:3000/"

Connection refused значит, что никто не слушает: ss -tlnp | grep ':3000' пуст — Gitea упала, идите в journald. Если в HTTP_ADDR внешний IP, а nginx стучится в 127.0.0.1, виноват конфиг. Минимум:

[server]
HTTP_ADDR = 127.0.0.1
DOMAIN = git.example.com
SSH_DOMAIN = git.example.com
ROOT_URL = https://git.example.com/

[security]
REVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.0/8,::1/128

Слэш в конце ROOT_URL обязателен. Блок nginx:

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-Proto $scheme;
    client_max_body_size 512M;
    proxy_request_buffering off;
}

Без X-Forwarded-Proto Gitea роняет формы, без REVERSE_PROXY_TRUSTED_PROXIES в аудите у всех будет 127.0.0.1. Живой перезагрузки конфига нет — нужен systemctl restart gitea. И закройте порт: ufw allow 22/tcp, ufw allow 80,443/tcp, ufw deny 3000/tcp.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Gitea

`Permission denied (publickey)`: SSH к репозиториям

SSH-транспортов у Gitea два, и путают их постоянно. Начните с ssh -T git@git.example.com. Правильный ответ — Hi there, ivan! You've successfully authenticated, but Gitea does not provide shell access. Получили его, а push падает — дело в хуках (следующий раздел); Permission denied (publickey) — разбираем транспорт.

Вариант A: системный sshd (по умолчанию). Gitea дописывает ключи в /home/git/.ssh/authorized_keys строками вида command="/usr/local/bin/gitea --config=... serv key-3",restrict ssh-ed25519 AAAA.... Два частых отказа, оба видны в journalctl -u ssh -n 50:

  • Authentication refused: bad ownership or modes for file /home/git/.ssh/authorized_keys — sshd игнорирует файл с лишними правами: chmod 700 /home/git/.ssh, chmod 600 /home/git/.ssh/authorized_keys, chmod 750 /home/git, всё под chown -R git:git /home/git/.ssh.
  • В логах пусто, отказ мгновенный — в /etc/ssh/sshd_config стоит AllowUsers без git. Сюрприз после хардненинга.

Строк с ключами нет вовсе? Верните их: sudo -u git gitea admin regenerate keys -c /etc/gitea/app.ini.

Вариант B: встроенный SSH-сервер Gitea. Включается START_SSH_SERVER = true и authorized_keys не использует — ключи берутся из базы. Ошибка тут одна: SSH_LISTEN_PORT — где Gitea слушает, SSH_PORT — что она пишет в клон-ссылку. Разошлись — пользователи копируют нерабочий адрес, поэтому в [server] ставьте оба в 2222 и открывайте порт (ufw allow 2222/tcp). Клон тогда выглядит как ssh://git@git.example.com:2222/team/app.git: короткая форма git@host:team/app.git порт не понимает.

Push падает: 413, обрыв соединения и сломанные хуки

HTTP 413 при push по HTTPS. Клиент видит:

error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413
fatal: the remote end hung up unexpectedly

Виноват nginx с дефолтом client_max_body_size 1m: любой push тяжелее мегабайта отрезается. Ставьте client_max_body_size 512M; и обязательно proxy_request_buffering off; — иначе nginx примет пуш во временный файл /var/lib/nginx/body, а на маленьком диске это ещё и no space left on device. Cloudflare в режиме прокси на бесплатном тарифе режет тело запроса на 100 МБ — первый push большого репозитория гоните по SSH.

Обрыв без кода 413. Смотрите строку выше в выводе клиента. remote: fatal: Out of memory, malloc failed (tried to allocate 268435456 bytes) означает, что серверный git pack-objects убит по нехватке памяти; подтверждение — journalctl -k | grep -i "killed process" даст Out of memory: Killed process 2117 (git). Совет «увеличьте http.postBuffer» бесполезен: это клиентский буфер. Работают swap на 2 ГБ и ограничение git в app.ini:

[git.config]
core.bigFileThreshold = 512m
pack.windowMemory = 256m
pack.packSizeLimit = 256m
pack.deltaCacheSize = 128m

error: cannot run hooks/pre-receive: No such file or directory. Классика после переезда или восстановления из бэкапа: в каждом репозитории лежат хуки с зашитым путём к gitea. Возвращает всё одна команда: sudo -u git gitea admin regenerate hooks -c /etc/gitea/app.ini.

LFS не работает. При Repository or object not found на git lfs push проверьте [server] LFS_START_SERVER = true, а если сервер включён — снова client_max_body_size: LFS шлёт файл одним PUT.

База: `database is locked`, ошибка 1071 и Postgres

SQLite и database is locked. SQLite допускает одного писателя. Пока работают два-три человека, всё хорошо; с вебхуками, зеркалами и Gitea Actions записи конфликтуют, и в журнале появляется PANIC: ... database is locked. Полумера — [database] SQLITE_TIMEOUT = 5000 (по умолчанию 500 мс): запросы будут ждать, а не падать. Ориентир: до 5–10 активных пользователей и без Actions SQLite честно тянет, дальше — Postgres.

Честный минус: штатного конвертера SQLite → Postgres нет. gitea dump кладёт в архив ту же базу; перенос идёт через сторонний pgloader плюс doctor check --all --fix. Проще выбрать Postgres на старте.

MySQL и Error 1071: Specified key was too long; max key length is 767 bytes. Схема требует utf8mb4 с ROW_FORMAT=DYNAMIC, на MySQL 5.7 это возня с innodb_large_prefix. Берите MySQL 8 или MariaDB 10.6+ и CHARSET = utf8mb4.

Postgres и pq: SSL is not enabled on the server. Локальная база без TLS — норма, Gitea просто просит её по умолчанию: дописывайте SSL_MODE = disable рядом с HOST = 127.0.0.1:5432. Соседняя pq: password authentication failed for user "gitea" лечится кавычками вокруг пароля и scram-sha-256 вместо peer в pg_hba.conf.

После обновления Gitea не стартует. Миграции схемы необратимы: запустив новую версию, вернуться на старый бинарник без дампа базы нельзя. Порядок — systemctl stop gitea, sudo -u git gitea dump -c /etc/gitea/app.ini --file /var/backups/gitea-$(date +%F).zip, замена бинарника, старт.

Тихие поломки: иноды, `git gc`, вебхуки и почта

Эти четыре всплывают не сразу, а через месяцы.

Кончились иноды, а место есть. Git хранит тысячи мелких объектов, и на ext4 с дефолтом (одна инода на 16 КБ) лимит наступает раньше гигабайтов. Симптом обманчив: No space left on device при df -h с 60% свободного места. Проверяйте df -i /var/lib/gitea, колонка IUse%.

Репозитории пухнут, потому что git gc не запускается. Сборка мусора выключена по умолчанию: [cron.git_gc_repos] ENABLED = false.

[cron.git_gc_repos]
ENABLED = true
SCHEDULE = @every 72h
TIMEOUT = 120s

Тайм-аут по умолчанию (60 секунд) на крупном репозитории не отработает: задача оборвётся, мусор останется. И не включайте бездумно [indexer] REPO_INDEXER_ENABLED = true: индекс в /var/lib/gitea/indexers/repos.bleve на сотне репозиториев занимает гигабайты.

Вебхук не доходит до внутреннего сервиса. Gitea по умолчанию запрещает запросы на приватные адреса: доставка на http://10.8.0.5:9000/hook падает с отказом «адрес не разрешён». Лечится [webhook] ALLOWED_HOST_LIST = private; * тоже работает, но это дыра в SSRF-защите.

Почта не уходит. С версии 1.18 адрес задаётся парой SMTP_ADDR и SMTP_PORT при PROTOCOL = smtps, а старый HOST игнорируется — после обновления с 1.17 почта из-за него молча не уходит. Проверка — кнопка тестового письма в админке. Ошибка dial tcp 93.184.216.34:25: i/o timeout — не опечатка, а закрытый исходящий 25-й порт: его блокируют почти все провайдеры, включая нас. Работайте через 465 или 587 (smtp+starttls).

Какой сервер под Gitea взять в MAATRIX

Половина разобранного выше — нехватка ресурсов, а не кривые руки. Сама Gitea скромна: в простое процесс держит 120–200 МБ RSS. Память съедают соседи: Postgres просит ещё 150–250 МБ, а пиковый git pack-objects при первом клоне монорепозитория берёт гигабайт.

Минимум: 1 vCPU, 2 ГБ RAM, 40 ГБ NVMe. Хватает команде до пяти человек и паре десятков репозиториев на SQLite. Ограничения: без Actions, без индексатора кода и с обязательным swap — иначе первый большой push кончится тем самым Out of memory, malloc failed.

Комфортно: 2 vCPU, 4 ГБ RAM, 80 ГБ NVMe. Локальный Postgres, git gc, поиск по коду, зеркала. Диск тут важнее процессора: история и LFS растут быстрее, чем кажется, и 40 ГБ кончаются примерно за год. Планируете CI на том же сервере — закладывайте 4 vCPU и 8 ГБ: сборки конкурируют с Gitea за память, первым падает git. Раскладка — в материале сколько RAM нужно для Gitea.

Локация — UK (Лондон). Gitea постоянно ходит наружу: зеркала с GitHub, образы для раннеров, npm и Go-модули в сборках — из Лондона это работает без обходных путей. Плюс 8–15 мс до Амстердама и Франкфурта, соседство с GDPR-контуром и рабочие 40–55 мс до Москвы: для git разница неощутима, узкое место — пропускная способность, а не задержка. Франция — то же самое для команды в ЕС. Россию выбирайте, когда код обязан оставаться в РФ по 152-ФЗ; США — если раннеры ходят к зарубежным ИИ-API.

Заказ занимает несколько минут: выбираете локацию и конфигурацию, отмечаете в каталоге приложение Gitea — сервер приедет с уже установленной и запущенной Gitea, вставлять команды не нужно. Автоустановка работает на Ubuntu и Debian, доступы появятся в личном кабинете, в разделе «Доступ»; останется завести администратора, прописать ROOT_URL и выпустить сертификат. Ручной порядок — в статье как установить и настроить Gitea на VPS, выбор — в сравнении Gitea против GitLab, конфигурации — на странице аренды VPS. Оплата — картами российских банков, по СБП, криптовалютой или токеном MAAT.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Gitea

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Clone показывает localhost:3000 вместо домена — где чинить?

В [server] файла /etc/gitea/app.ini: пропишите ROOT_URL = https://git.example.com/ со слэшем на конце, а также DOMAIN и SSH_DOMAIN, затем systemctl restart gitea — конфиг на лету Gitea не перечитывает.

ssh -T git@host отвечает приветствием, а push всё равно падает. Почему?

Аутентификация прошла, дело в репозитории: обычно это сломанные git-хуки после переезда. Выполните sudo -u git gitea admin regenerate hooks -c /etc/gitea/app.ini и проверьте владельца /var/lib/gitea/data/gitea-repositories.

Push по HTTPS перестал работать после включения 2FA — это баг?

Нет: с 2FA пароль от аккаунта в git не принимается, и вы видите fatal: Authentication failed. Заведите токен со скоупом write:repository и подставляйте его вместо пароля либо переходите на SSH.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.