настройка self-hosted, часть 1: первичная настройка хоста и безопасности

Это первая часть гайда-памятки по настройке self-hosted-инфраструктуры. Гайд написан под Ubuntu 24.04 LTS на DigitalOcean. На других дистрибутивах базовая логика та же, отличаться будут только команды и расположение конфигов. Большинство разделов независимы и опциональны: набор собран под мои задачи, берите нужное. Разделы 1-2 (юзер и SSH) стоит сделать первыми и целиком.

Содержание:


Чеклист

Безопасность

Базовая система

Инфраструктура

На локальной машине


Главное

Минимальные привилегии. Каждый сервис и каждый юзер должны иметь минимум прав. Не запускаем ничего от root, не открываем ненужные порты, не даем sudo юзеру, которому хватит непривилегированного шелла.

Минимизация поверхности атаки. Базовое правило: deny по дефолту, разрешать точечно. VPS должен снаружи отвечать только на 22, 80 и 443, а все остальное - слушать на 127.0.0.1 либо проксироваться.


1. Настройка non-root юзера и привязка ssh-ключа

На VPS создаем отдельного пользователя username для повседневной работы (замени username на свое имя). Команды выполняем под root на свежем дроплете. Дальше мы копируем ssh-ключ из /root/.ssh/authorized_keys - он там есть, только если при создании дроплета был выбран SSH-ключ. Проверяем, что файл не пустой:

cat ~/.ssh/authorized_keys      # должен показать твой публичный ключ

Если пусто (дроплет создан с паролем) - сначала заливаем ключ с локальной машины (ssh-copy-id username@<IP_VPS>, пока пароль еще разрешен), иначе после раздела 2 (отключение паролей) потеряете доступ.

adduser username
usermod -aG sudo username       # даем права sudo
mkdir -p /home/username/.ssh
cp ~/.ssh/authorized_keys /home/username/.ssh/
chown -R username:username /home/username/.ssh
chmod 700 /home/username/.ssh
chmod 600 /home/username/.ssh/authorized_keys

2. Настройка SSH

Отключаем небезопасные способы входа, ограничиваем число попыток и убиваем зависшие сессии. Дефолтный /etc/ssh/sshd_config напрямую не трогаем - при обновлении пакета openssh-server apt спросит про конфликт, и можно случайно вернуть PermitRootLogin yes. Кладем свои настройки отдельным файлом в drop-in каталог (отдельные .conf-файлы, которые подхватываются в дополнение к основному конфигу):

sudo nano /etc/ssh/sshd_config.d/99-ssh.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
AllowUsers username
MaxAuthTries 3
LoginGraceTime 20
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowAgentForwarding no

Что делает каждая строка:

В дефолтном sshd_config сверху стоит Include /etc/ssh/sshd_config.d/*.conf. Sshd для большинства директив берет первое встретившееся значение (исключения отмечены в man sshd_config), поэтому drop-in, подключенный Include в самом верху, перебивает то, что идет ниже в основном конфиге.

Проверяем синтаксис до применения и перечитываем конфиг, если ок:

sudo /usr/sbin/sshd -t
sudo systemctl reload ssh

На Ubuntu 24.04 SSH работает через socket-activation - порт 22 слушает systemd-юнит ssh.socket, а сам sshd поднимается по запросу. Директивы аутентификации и таймаутов из нашего файла подхватываются обычным reload.

Не закрывая текущую root-сессию, в новом окне проверяем вход под non-root по ключу: ssh username@<IP_VPS>. Только убедившись, что вход работает, закрываем root.

3. Файрвол UFW

UFW (Uncomplicated Firewall) - это обертка над iptables. Закрываем все входящее, оставляем 22, 80, 443:

sudo ufw default deny incoming        # дефолт - блокировать входящий трафик
sudo ufw default allow outgoing       # дефолт - разрешить исходящий трафик
# открываем порты на ssh
sudo ufw allow 22/tcp
# открываем порты на http(s), если нужно сервисам
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# включаем файрвол
sudo ufw enable
# посмотреть текущее состояние
sudo ufw status verbose

Вывод последней команды будет таким:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
80/tcp                     ALLOW IN    Anywhere
443/tcp                    ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)
80/tcp (v6)                ALLOW IN    Anywhere (v6)
443/tcp (v6)               ALLOW IN    Anywhere (v6)

Если в будущем планируется ставить какие-то сервисы, то они должны работать на 127.0.0.1 и проксироваться через реверс-прокси (например, Caddy) на 80/443. Не открываем новые порты наружу без причины.

4. fail2ban от брутфорса

fail2ban защищает от перебора паролей. Он читает логи, находит неудачные попытки логина и временно банит IP.

sudo apt install -y fail2ban

На Ubuntu пакет fail2ban из коробки включает jail для SSH через файл /etc/fail2ban/jail.d/defaults-debian.conf. То есть после apt install сервис уже защищает SSH с дефолтными параметрами - больше ничего делать не надо. Проверим:

sudo fail2ban-client status sshd

Должно показать Status for the jail: sshd и счетчики Currently failed, Currently banned.

Если хочется свои параметры

Создаем отдельный файл /etc/fail2ban/jail.d/sshd.conf:

[sshd]
maxretry = 3
bantime = 3600
findtime = 600

maxretry - сколько неудачных попыток входа до бана, bantime - длительность бана в секундах (3600 = час), findtime - окно в секундах, внутри которого считаются эти попытки (600 = 10 минут). Комментарии пишутся через ; .

Файлы в jail.d/ читаются в алфавитном порядке, секции с одинаковым именем мержатся, и значение из файла, прочитанного позже, побеждает. sshd.conf идет после defaults-debian.conf, поэтому наши параметры перекрывают дефолтные.

jail.local - тоже официальный способ переопределять настройки, равноценный jail.d/. В этом гайде используем отдельный файл jail.d/sshd.conf, чтобы свои правки лежали обособленно. Если смешивать оба, помни порядок чтения: jail.conf -> jail.d/*.conf -> jail.local -> jail.d/*.local, и jail.local как прочитанный позже перекроет jail.d/sshd.conf.

Применить:

sudo systemctl reload fail2ban
sudo fail2ban-client status sshd

5. Автообновления безопасности

unattended-upgrades - механизм автоматических апдейтов Ubuntu/Debian.

sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

На вопрос “Automatically download and install stable updates?” отвечаем yes.

--priority=low нужен, чтобы мы получили диалоговое окно с подтверждением автоапдейтов.

Проверяем, в /etc/apt/apt.conf.d/20auto-upgrades должно быть:

# Раз в день обновляем список пакетов
APT::Periodic::Update-Package-Lists "1";
# Раз в день запускаем установку обновлений
APT::Periodic::Unattended-Upgrade "1";

Дополнительно я настроил автоматическую очистку старых версий ядер. На VPS раздел /boot обычно небольшой, и пакеты со старыми ядрами могут его забить, из-за чего следующее обновление ядра упадет.

sudo nano /etc/apt/apt.conf.d/52unattended-upgrades-local
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";

Перезагрузку после обновлений ядра оставляем ручной: автоматический ребут на проде нежелателен, момент лучше выбрать самому. Раз в месяц можно делать sudo reboot.

6. Базовые утилиты

sudo apt install -y curl wget htop ncdu tmux tree

htop - мониторинг процессов, ncdu - найти кто ест диск, tmux - детачаемые сессии, tree - визуализация директорий.

7. Часовой пояс и hostname

Часовой пояс необходимо настроить, чтобы логи писались и cron-таски работали по твоему времени, а не по UTC.

Hostname задаем, чтобы в приглашении шелла и логах видеть осмысленное имя машины, а не случайное, и различать хосты при нескольких серверах.

sudo timedatectl set-timezone <таймзона>
sudo hostnamectl set-hostname <хостнейм>

8. Выделение swap

При нехватке памяти Linux убивает процесс через OOM-killer - механизм ядра, который при исчерпании RAM выбирает и завершает процесс. Swap не отменяет OOM, но дает буфер - при кратковременной нехватке памяти страницы вытесняются на диск, и процесс не падает сразу. Размер берут от 2x RAM. Для 2 GB я взял 4G с запасом.

sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h

9. Git, Docker + Docker Compose

sudo apt install -y git

Дока Docker Engine: https://docs.docker.com/engine/install/ubuntu/

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

sudo usermod -aG docker $USER

После добавления в группу docker нужно перелогиниться, чтобы изменения применились. Проверка:

docker run hello-world

10. DigitalOcean Metrics Agent

Разделы 10-11 про базовую настройку метрик средствами DO. Помимо этого у меня работает Prometheus + Grafana / Alerts, об этом будет в отдельном гайде.

Дока DO Metrics Agent: https://docs.digitalocean.com/products/monitoring/how-to/install-metrics-agent/

Без do-agent DO видит только базовые внешние метрики. С ним работают расширенные графики, алерты по памяти/диску/CPU.

curl -sSL https://repos.insights.digitalocean.com/install.sh -o /tmp/install.sh
less /tmp/install.sh                     # просмотреть, что делает
sudo bash /tmp/install.sh                # запустить
systemctl status do-agent                # проверить

Агент работает под отдельным пользователем do-agent.

11. Resource Alerts в DO

После установки do-agent создаем в Monitoring -> Create Alert Policy:

Пороги со временем (например, через неделю наблюдений) стоит скорректировать под реальную нагрузку.

12. SSH-конфиг на локальной машине

На локальной машине добавить в ~/.ssh/config:

Host vps
    HostName <IP_VPS>
    User username
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

vps - произвольный алиас для подключения. ServerAliveInterval шлет keepalive, чтобы соединение не отваливалось при долгом простое. IdentitiesOnly yes заставляет ssh предлагать только указанный IdentityFile, а не все ключи из агента - это важно в паре с MaxAuthTries 3 из раздела 2, иначе при многих ключах в агенте можно упереться в Too many authentication failures.

Теперь вместо ssh username@<IP_VPS> достаточно ssh vps.

13. Бэкапы DO

Бэкап средствами DigitalOcean: Droplets -> дроплет -> Backups & Snapshots -> Setup Automated Backups -> Weekly Backups.

Стоит 20% стоимости дроплета.

14. Лимит логов journald

Ограничим journald место под логи. Основной /etc/systemd/journald.conf напрямую не трогаем (по той же причине, что и sshd_config в разделе 2):

sudo mkdir -p /etc/systemd/journald.conf.d
sudo nano /etc/systemd/journald.conf.d/00-server.conf
[Journal]
SystemMaxUse=500M

Применить и подрезать уже накопившиеся логи разово:

sudo systemctl restart systemd-journald
sudo journalctl --vacuum-size=500M

Полезные приемы journalctl:

journalctl -f                              # live tail всего журнала
journalctl -f -u ssh                       # live tail сервиса
journalctl --since "1 hour ago"            # за последний час
journalctl --since "2026-05-17 14:00"      # с конкретного времени
journalctl -k                              # только сообщения ядра
journalctl -p err -b -u ssh                # только errors+, текущая загрузка, конкретный юнит