Эта статья написана по следам реальной установки GitLab CE на домашний сервер. Задача казалась стандартной — пока не выяснилось, что провайдер блокирует входящий 443 порт, диск работает за пределами паспортного ресурса по циклам паркинга, а в GitLab 18.x убрали несколько параметров конфига которые есть в большинстве гайдов. Здесь собрано всё что нужно знать чтобы не наступать на те же грабли.

Важно понимать: часть шагов этого гайда продиктована конкретными условиями — ограниченные ресурсы сервера, несколько сайтов на одной машине, заблокированный 443 порт провайдером. Там где это важно — я явно это указываю, чтобы вы могли пропустить лишнее или воспользоваться более простым встроенным решением GitLab.

Что мы будем делать

Берём домашний сервер на Ubuntu Server 22.04 и поднимаем на нём полноценный GitLab с правильной архитектурой:

  • Проверка здоровья HDD и защита от износа
  • SWAP для стабильной работы при нехватке RAM
  • GitLab CE с отключённым встроенным Nginx
  • Container Registry для хранения Docker-образов
  • SSL-терминация на внешнем сервере с универсальным проксированием
  • GitLab Exporter для подключения к внешнему Prometheus

Содержание

  1. Подготовка сервера
  2. Установка GitLab CE
  3. Настройка gitlab.rb
  4. Nginx на домашнем сервере
  5. Проксирование через внешний сервер
  6. Настройка DNS
  7. Подключение к Prometheus
  8. Проверка работоспособности

1. Подготовка сервера

1.1. Проверяем здоровье диска

Прежде чем нагружать сервер — проверьте состояние диска. GitLab активно использует диск, и если он на излёте, лучше знать об этом заранее:

apt install -y smartmontools
smartctl -H /dev/sda        # общий вердикт: PASSED или FAILED
smartctl -a /dev/sda        # полная информация

Обратите внимание на эти параметры:

  • Reallocated_Sector_Ct — переназначенные сектора. Если больше 0 — диск уже имеет бэды
  • Current_Pending_Sector — потенциальные бэды, ожидающие переназначения
  • Load_Cycle_Count — количество парковок головки. Для ноутбучных HDD паспортный ресурс 300 000–600 000 циклов
  • Power_On_Hours — суммарное время работы диска

Важно: Если используете ноутбучный HDD, Load_Cycle_Count может быть критически высоким даже при нулевых бэдах. Такой диск паркует головку при каждом простое чтобы экономить батарею — на сервере работающем 24/7 это накапливается быстро. Если счётчик уже превышает паспортный ресурс — следующий шаг обязателен.

1.2. Отключаем агрессивный паркинг HDD

Актуально только для ноутбучных HDD. Если у вас серверный HDD или SSD — пропускайте этот шаг.

Отключаем агрессивный паркинг — это остановит дальнейший рост Load_Cycle_Count:

apt install -y hdparm
hdparm -B 254 /dev/sda   # 254 = минимальный APM, почти отключён

Проверяем что применилось — в выводе должно быть APM_level = 254. Делаем настройку постоянной через udev-правило, иначе после перезагрузки вернётся агрессивный паркинг:

echo 'ACTION=="add", SUBSYSTEM=="block", KERNEL=="sda", RUN+="/sbin/hdparm -B 254 /dev/sda"' \
  > /etc/udev/rules.d/69-hdparm.rules

Через несколько дней можно проверить что счётчик перестал активно расти:

smartctl -a /dev/sda | grep Load_Cycle_Count

1.3. Настраиваем SWAP

GitLab в пике потребляет 3–5 GiB RAM. Если на сервере параллельно работают другие сервисы и памяти немного — можно упереться в потолок во время тяжёлых операций, например git push с CI. SWAP здесь не рабочий режим, а страховка от падения.

Если у вас много RAM или GitLab единственный сервис — этот раздел можно пропустить или ограничиться 4 GiB SWAP. На сервере с 16+ GiB RAM и только GitLab SWAP практически не используется.

Что такое SWAP: часть диска, работающая как резервная RAM. Когда оперативная память заканчивается — без SWAP ядро убивает процессы (OOM Killer), GitLab просто падает. Со SWAP вместо падения ядро перекладывает «холодные» данные на диск — медленно, но сервис не упадёт.

Проверяем что уже есть на сервере:

swapon --show

Если увидели /swap.img с небольшим размером — лучше увеличить его, чем создавать второй файл:

# Отключаем текущий swap
swapoff /swap.img

# Дописываем ещё 4G к существующим 4G (итого 8G)
# Размер подбирайте под объём вашей RAM и количество сервисов
dd if=/dev/zero bs=1M count=4096 >> /swap.img

# Переинициализируем и включаем
mkswap /swap.img
swapon /swap.img

Если /swap.img не было — создаём с нуля:

fallocate -l 8G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

Снижаем агрессивность использования SWAP — чтобы система не трогала его без крайней необходимости:

echo 'vm.swappiness=10' >> /etc/sysctl.conf
sysctl -p

Проверяем результат:

swapon --show
free -h

Проверяем автозапуск — файл SWAP должен быть прописан в /etc/fstab:

cat /etc/fstab | grep swap

Симуляция перезагрузки без самой перезагрузки:

swapoff /swap.img && swapon -a
swapon --show  # должен подняться обратно

1.4. Устанавливаем зависимости и Nginx

В этом гайде мы отключаем встроенный Nginx GitLab и используем системный — это нужно потому что на сервере живут другие сайты и Nginx управляет всеми виртуальными хостами централизованно.

Если GitLab единственный сервис на сервере — системный Nginx не нужен. GitLab поставляется со своим встроенным Nginx который уже настроен под него и поддерживается пакетом. В таком случае пропустите установку Nginx здесь, а в разделе 3 не отключайте nginx['enable'].

apt update && apt upgrade -y
apt install -y curl ca-certificates tzdata perl postfix nginx
# В диалоге postfix выбираем "Internet Site"
systemctl enable nginx

2. Установка GitLab CE

2.1. Добавляем репозиторий

curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | bash

2.2. Устанавливаем GitLab

Указываем внешний URL сразу при установке — это нужно GitLab для правильной генерации ссылок и CSRF-защиты:

EXTERNAL_URL="https://git.example.com" apt install -y gitlab-ce  # ЗАМЕНИТЕ ДОМЕН

Установка занимает 5–10 минут и требует ~3–4 GiB RAM в пике. Если сервер в этот момент выключится — проверьте логи после перезагрузки:

dpkg -l | grep gitlab-ce   # ii = установлен, iF = установлен но не сконфигурирован
dmesg | grep -i "oom\|killed" | tail -20

Если статус iF — пакет установился, но конфигурация не завершилась. Лечится запуском:

gitlab-ctl reconfigure

Классическая ошибка в GitLab 18.x: reconfigure падает с ошибкой Reading unsupported config value grafana. В этой версии встроенная Grafana убрана, строка grafana['enable'] = false больше не поддерживается. Найдите и удалите её из gitlab.rb перед повторным запуском reconfigure:

grep -n grafana /etc/gitlab/gitlab.rb  # найдёт номер строки
sed -i 'Xd' /etc/gitlab/gitlab.rb     # ЗАМЕНИТЕ X на найденный номер строки

3. Настройка gitlab.rb

3.1. Основная конфигурация

GitLab по умолчанию поднимает собственный Nginx и пытается получить SSL-сертификат через Let's Encrypt. Если GitLab единственный сервис и 443 порт открыт — это удобно и работает из коробки. В нашем случае оба механизма отключены: SSL терминируется на внешнем сервере, а Nginx управляется системно.

nano /etc/gitlab/gitlab.rb
external_url 'https://git.example.com'  # ЗАМЕНИТЕ НА ВАШ ДОМЕН

# Отключаем встроенный Nginx — используем системный
# Если GitLab единственный сервис — уберите эту строку
nginx['enable'] = false

# Отключаем встроенный Let's Encrypt — сертификаты получаем сами
# Если порт 443 открыт и GitLab один на сервере — уберите эту строку,
# Let's Encrypt настроится автоматически
letsencrypt['enable'] = false

# GitLab слушает локально на порту 8181
gitlab_workhorse['listen_network'] = "tcp"
gitlab_workhorse['listen_addr'] = "127.0.0.1:8181"

# Оптимизация под ограниченные ресурсы сервера с несколькими сервисами.
# Если GitLab единственный сервис или сервер мощный — уберите эти строки,
# GitLab выставит оптимальные значения сам исходя из CPU и RAM
puma['worker_processes'] = 2
puma['min_threads'] = 1
puma['max_threads'] = 2
sidekiq['concurrency'] = 5

# Встроенный Prometheus и Alertmanager отключены — используется внешний мониторинг
# Если своего Prometheus нет — уберите эти строки, GitLab поднимет мониторинг сам
prometheus['enable'] = false
alertmanager['enable'] = false
# grafana['enable'] = false  — НЕ добавлять! В GitLab 18.x этого параметра нет

# GitLab Exporter для подключения к внешнему Prometheus
# Если внешнего мониторинга нет — пропустите этот блок
prometheus_monitoring['enable'] = true
node_exporter['enable'] = false
redis_exporter['enable'] = false
postgres_exporter['enable'] = false
gitlab_exporter['enable'] = true
gitlab_exporter['listen_address'] = '0.0.0.0'
gitlab_exporter['listen_port'] = '9168'

# Container Registry — свой Docker Hub
registry['enable'] = true
registry_external_url 'https://dcr.example.com'  # ЗАМЕНИТЕ НА ВАШ ДОМЕН

# Pages не нужны
gitlab_pages['enable'] = false

# Оптимизация PostgreSQL под ограниченные ресурсы.
# На мощном сервере — уберите эти строки
postgresql['shared_buffers'] = "128MB"
postgresql['max_worker_processes'] = 2

3.2. Применяем конфигурацию

gitlab-ctl reconfigure

Должно завершиться строкой gitlab Reconfigured!. Проверяем что все сервисы запустились:

gitlab-ctl status

В списке должны быть run: для: gitaly, gitlab-exporter, gitlab-workhorse, postgresql, puma, redis, registry, sidekiq. Если что-то в статусе down — смотрим логи:

gitlab-ctl tail

4. Nginx на домашнем сервере

Этот раздел актуален только если вы отключили встроенный Nginx GitLab в предыдущем разделе. Если GitLab единственный сервис — пропускайте.

Домашний Nginx работает только по HTTP на порту 80 — SSL здесь не нужен, шифрование обеспечивает внешний сервер. Редирект на HTTPS добавлять не нужно — иначе получим бесконечный цикл.

4.1. Конфиг для GitLab

nano /etc/nginx/sites-available/git.example.com  # ЗАМЕНИТЕ ДОМЕН
upstream gitlab {
    server 127.0.0.1:8181;
}

server {
    listen 80;
    server_name git.example.com;  # ЗАМЕНИТЕ НА ВАШ ДОМЕН

    client_max_body_size 250m;  # для больших репозиториев и пушей

    location / {
        proxy_pass http://gitlab;
        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 https;  # говорим GitLab что снаружи HTTPS
        proxy_set_header X-Forwarded-Ssl on;       # необходимо для корректной CSRF-защиты
        proxy_read_timeout 300;
        proxy_connect_timeout 300;
        proxy_send_timeout 300;
    }
}

Важно: Заголовки X-Forwarded-Proto https и X-Forwarded-Ssl on — не опциональные. Без них GitLab считает что работает по HTTP, и при попытке войти на сайт выдаёт ошибку 422: The change you requested was rejected. Это CSRF-защита, реагирующая на несоответствие между external_url (HTTPS) и реальным протоколом запроса (HTTP).

4.2. Конфиг для Container Registry

nano /etc/nginx/sites-available/dcr.example.com  # ЗАМЕНИТЕ ДОМЕН
server {
    listen 80;
    server_name dcr.example.com;  # ЗАМЕНИТЕ НА ВАШ ДОМЕН

    client_max_body_size 0;           # без ограничений — Docker-образы могут быть большими
    chunked_transfer_encoding on;

    location / {
        proxy_pass http://127.0.0.1:5000;  # порт Registry по умолчанию
        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_read_timeout 300;
        proxy_connect_timeout 300;
        proxy_send_timeout 300;
    }
}

4.3. Активируем конфиги

ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/   # ЗАМЕНИТЕ
ln -s /etc/nginx/sites-available/dcr.example.com /etc/nginx/sites-enabled/   # ЗАМЕНИТЕ
nginx -t
systemctl reload nginx

Проверяем что GitLab отвечает локально:

curl -I http://127.0.0.1:8181

Должен вернуть 302 Found с редиректом на /users/sign_in — GitLab работает.

5. Проксирование через внешний сервер

Этот раздел нужен только если провайдер блокирует входящий 443 порт. Сначала проверьте — с любой внешней машины выполните:

nc -zv ВАШ_IP 80
nc -zv ВАШ_IP 443

Если 80 отвечает succeeded, а 443 висит и выдаёт Connection timed out — провайдер режет входящий HTTPS. Если оба порта открыты — этот раздел пропускайте, настройте SSL через встроенный certbot или Let's Encrypt GitLab напрямую на домашнем сервере.

При заблокированном 443 решение — терминировать HTTPS на внешнем сервере и проксировать трафик на домашний по открытому 80 порту. Итоговая схема:

Пользователь
     │
     ▼
Внешний сервер :443 (SSL termination, Nginx)
     │  proxy_pass по $host → домашний :80
     ▼
Домашний сервер :80 (Nginx)
     │  server_name
     ├── git.example.com  → GitLab :8181
     └── dcr.example.com  → Registry :5000

5.1. Подготовка общего файла настроек проксирования

Выносим общие настройки в отдельный файл — чтобы при добавлении новых сайтов не копировать одни и те же строки. Подключаемся к внешнему серверу:

mkdir -p /etc/nginx/includes
nano /etc/nginx/includes/proxy-to-home.conf
proxy_pass http://IP_ДОМАШНЕГО_СЕРВЕРА;  # ЗАМЕНИТЕ
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_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
client_max_body_size 250m;

5.2. Получаем SSL-сертификаты

DNS уже должен указывать на внешний сервер — проверяем:

ping git.example.com   # должен вернуть IP внешнего сервера

Классическая ошибка: если в конфиге Nginx уже прописан путь к сертификату которого ещё нет — certbot упадёт, потому что Nginx не стартует без файла сертификата. Порядок важен: сначала временно убрать конфиг из sites-enabled, перезапустить Nginx, получить сертификат, вернуть конфиг.

apt install -y certbot python3-certbot-nginx

certbot certonly --nginx \
  -d git.example.com \
  -d dcr.example.com \
  --email your@email.com \   # ЗАМЕНИТЕ
  --agree-tos \
  --non-interactive

Сертификаты действуют 90 дней. Certbot автоматически настраивает systemd-таймер для обновления — проверяем:

certbot renew --dry-run

5.3. Конфиг для GitLab и Registry на внешнем сервере

nano /etc/nginx/sites-available/home-proxy
# HTTP → HTTPS для всех доменов домашнего сервера
server {
    listen 80;
    server_name git.example.com dcr.example.com;  # ЗАМЕНИТЕ ДОМЕНЫ
    return 301 https://$host$request_uri;
}

# GitLab
server {
    listen 443 ssl;
    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;     # ЗАМЕНИТЕ
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
        include /etc/nginx/includes/proxy-to-home.conf;
    }
}

# Container Registry
server {
    listen 443 ssl;
    server_name dcr.example.com;  # ЗАМЕНИТЕ

    ssl_certificate     /etc/letsencrypt/live/git.example.com/fullchain.pem;   # ЗАМЕНИТЕ
    ssl_certificate_key /etc/letsencrypt/live/git.example.com/privkey.pem;     # ЗАМЕНИТЕ
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    client_max_body_size 0;
    chunked_transfer_encoding on;

    location / {
        include /etc/nginx/includes/proxy-to-home.conf;
    }
}
ln -s /etc/nginx/sites-available/home-proxy /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

5.4. Как добавлять новые сайты в будущем

Схема универсальная — новый сайт добавляется в четыре шага и не требует трогать уже настроенные конфиги.

Шаг 1 — DNS: добавляем A-запись в панели регистратора, указываем на IP внешнего сервера.

Шаг 2 — SSL на внешнем сервере:

certbot certonly --nginx -d newsite.example.com --email your@email.com --agree-tos --non-interactive

Шаг 3 — добавляем блок в home-proxy на внешнем сервере:

server {
    listen 80;
    server_name newsite.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name newsite.example.com;

    ssl_certificate     /etc/letsencrypt/live/newsite.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/newsite.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
        include /etc/nginx/includes/proxy-to-home.conf;
    }
}
nginx -t && systemctl reload nginx

Шаг 4 — конфиг на домашнем сервере:

server {
    listen 80;
    server_name newsite.example.com;

    location / {
        # ваш сайт
    }
}
nginx -t && systemctl reload nginx

6. Настройка DNS

DNS-записи для GitLab и Registry должны указывать на IP внешнего сервера — именно он принимает HTTPS и проксирует дальше. В панели регистратора добавляем A-записи:

git.example.com   →  IP_ВНЕШНЕГО_СЕРВЕРА   TTL 300
dcr.example.com   →  IP_ВНЕШНЕГО_СЕРВЕРА   TTL 300

Проверяем распространение DNS:

ping git.example.com   # должен вернуть IP внешнего сервера
ping dcr.example.com

Важно: Если ранее получали сертификаты на домашнем сервере — после смены DNS их стоит удалить. Certbot при попытке обновления будет падать, потому что домен больше не резолвится на домашний IP:

certbot delete --cert-name git.example.com

7. Подключение к Prometheus

Этот раздел актуален только при наличии внешнего Prometheus. Если своего мониторинга нет — GitLab при включённом встроенном Prometheus поднимает его сам без каких-либо правок конфига. Если хотите выстроить полноценную систему мониторинга с нуля — у меня есть отдельный гайд: Мониторинг серверов с Prometheus и Grafana: с нуля до централизованных дашбордов.

GitLab Exporter собирает метрики самого GitLab на уровне приложения: количество репозиториев, активных пользователей, время ответа CI-пайплайнов, статистику фоновых задач Sidekiq. Именно его мы подключаем к внешнему Prometheus.

7.1. Открываем порт экспортера

На домашнем сервере открываем порт GitLab Exporter только для сервера с Prometheus — не для всего интернета:

ufw allow from IP_PROMETHEUS_СЕРВЕРА to any port 9168  # ЗАМЕНИТЕ IP
ufw reload

7.2. Добавляем target в Prometheus

На сервере с Prometheus добавляем в prometheus.yml:

scrape_configs:
  - job_name: 'gitlab'
    static_configs:
      - targets: ['IP_ДОМАШНЕГО_СЕРВЕРА:9168']  # ЗАМЕНИТЕ IP
        labels:
          instance: 'home'
          service: 'gitlab'
systemctl restart prometheus

Проверяем что метрики доступны — открываем в браузере http://IP_ДОМАШНЕГО_СЕРВЕРА:9168/metrics с IP сервера Prometheus. Должна вернуться страница с метриками.

8. Проверка работоспособности

8.1. Базовая проверка

Открываем в браузере:

  • https://git.example.com — открывается страница входа GitLab
  • http://git.example.com — редиректит на HTTPS
  • https://git.example.com/admin/ — админка доступна

8.2. Меняем пароль root

Начальный пароль хранится в файле и действителен только 24 часа:

cat /etc/gitlab/initial_root_password

Входим на сайт с логином root и этим паролем. Сразу меняем: аватар в правом верхнем углу → Edit profilePassword. Если пароль уже истёк — сбрасываем через консоль без старого пароля:

gitlab-rake "gitlab:password:reset[root]"

8.3. Статус сервисов

gitlab-ctl status  # все должны быть run:
systemctl status nginx

8.4. Мониторинг логов

gitlab-ctl tail               # все логи GitLab в реальном времени
tail -f /var/log/nginx/error.log

8.5. Container Registry

Container Registry — это личный Docker Hub. Вместо того чтобы хранить образы на публичном hub.docker.com, вы пушите их на свой сервер. Основной сценарий — CI/CD: код запушен в GitLab, GitLab автоматически собирает Docker-образ, сохраняет в Registry, деплоит на сервер.

Проверяем через веб-интерфейс: любой проект → DeployContainer Registry. Registry пустой до первого пуша — это нормально.

Для работы с Registry с локальной машины:

docker login dcr.example.com   # логин и пароль как в GitLab
docker build -t dcr.example.com/myproject/myapp:latest .
docker push dcr.example.com/myproject/myapp:latest

8.6. Тест автозапуска

reboot
# После перезагрузки:
gitlab-ctl status
systemctl status nginx
swapon --show

Все три команды должны показывать что сервисы поднялись автоматически.

Заключение

Теперь у вас полноценный GitLab на домашнем железе:

  • Диск защищён от быстрого износа через отключение агрессивного паркинга
  • SWAP страхует от падения при нехватке RAM
  • GitLab работает через системный Nginx — сервер готов к хостингу других сайтов
  • HTTPS терминируется на внешнем сервере — блокировка 443 провайдером обойдена
  • Container Registry доступен для хранения Docker-образов
  • GitLab Exporter подключён к внешнему Prometheus
  • Универсальная схема проксирования позволяет добавлять новые сайты в четыре шага

При возникновении проблем первым делом смотрите логи. gitlab-ctl tail показывает что происходит внутри GitLab, /var/log/nginx/error.log — проблемы на уровне Nginx. Большинство ошибок подробно описаны там.

Следите за здоровьем диска если используете ноутбучный HDD — раз в несколько месяцев прогоняйте smartctl -H /dev/sda и проверяйте что Load_Cycle_Count больше не растёт активно.