Коротко. Сразу после выдачи VPS, до выкладки проекта, нужно сделать восемь вещей: проверить ресурсы и тип виртуализации, завести непривилегированного пользователя с sudo, перенести SSH-ключ и отключить вход по паролю, поднять firewall и fail2ban, накатить обновления и включить автоустановку security-патчей, задать hostname, часовой пояс и NTP, добавить swap и лимиты открытых файлов. Веб-стек, SSL, бэкапы и мониторинг ставятся сразу следом — не «потом».
Почему первые 30 минут важнее последующего тюнинга
Свежий VPS в момент выдачи — это машина с публичным IP, открытым SSH и, как правило, паролем root, который вам прислали в письме. Сканеры находят такой хост за минуты: типовой сервер начинает получать переборы SSH ещё до того, как вы успели придумать имя проекту. Всё, что вы сделаете в первые полчаса, определяет, будет ли сервер жить годами или превратится в майнер-ферму на второй неделе.
Вторая причина — стоимость исправления. Поменять дистрибутив, разметку диска, имя пользователя или схему бэкапов легко на пустом сервере и мучительно после того, как на нём крутится прод с базой и почтой. Порядок шагов ниже выстроен именно по этой логике: сначала необратимое и фундаментальное, потом всё остальное.
Правило: не выкладывайте проект на сервер, где ещё не настроены firewall, автоматические security-обновления и бэкапы. Сайт, который «пока в тесте», индексируется, сканируется и ломается ровно так же, как продакшен.

Шаг 0. Какую ОС выбрать для сервера и почему не «самую свежую»
Для веб-сервера подходит LTS-ветка: Ubuntu Server LTS или Debian stable. Мотив прост — предсказуемость. LTS-релизы получают security-обновления годами без смены мажорных версий системных библиотек, а значит, ваш сервер не сломается от планового обновления. Ubuntu LTS выходит раз в два года и держит стандартную поддержку около пяти лет, Debian stable живёт примерно три года плюс продление силами LTS-команды.
«Самый свежий» промежуточный релиз (interim) — плохой выбор для прода: короткий срок поддержки (порядка девяти месяцев), после чего вы обязаны делать полное обновление дистрибутива на живом сервере. Разница в версии nginx или PHP решается подключением официального репозитория пакета, а не сменой ОС.
| Критерий | Ubuntu Server LTS | Debian stable | Промежуточный релиз |
|---|---|---|---|
| Срок поддержки | Годы, фиксирован | Годы, фиксирован | Месяцы |
| Свежесть пакетов | Средняя | Консервативная | Высокая |
| Готовые инструкции в сети | Максимум | Много | Мало |
| Риск сломать прод обновлением | Низкий | Очень низкий | Высокий |
| Кому подходит | Типовой веб-проект | Долгоживущий сервер, минимум изменений | Стенд, эксперименты |
Отдельно про панели: образы с преднастроенными панелями управления экономят время на старте, но забирают контроль над конфигами nginx и правилами firewall. Если вы читаете этот чек-лист, вам, скорее всего, нужен чистый образ. Разницу между тарифами и типами хостинга разбираем в статье Виртуальный хостинг, VPS или выделенный сервер.
Шаг 1. Проверить, что вам реально выдали
До установки софта убедитесь, что железо соответствует тарифу и что это не «оверселлинг» с ядром, поделённым на десятерых. Три минуты команд экономят день разбирательств с поддержкой.
# тип виртуализации: kvm — полноценная ВМ, lxc/openvz — контейнер
systemd-detect-virt
# процессор: число ядер, модель, флаги
lscpu | head -20
# память и swap (по умолчанию swap на VPS часто отсутствует)
free -h
# диски, разделы, свободное место
lsblk
df -hT
# сетевые интерфейсы и адреса
ip -br address
# версия ядра и дистрибутива
uname -r
cat /etc/os-release
Что смотреть в выводе:
- Контейнерная виртуализация (lxc, openvz) — общее ядро с хостом. Нельзя загрузить свои модули, часто нельзя пользоваться собственным swap и некоторыми sysctl, могут не работать Docker-фичи и nftables. Для веб-сайта это обычно приемлемо, для нестандартного стека — блокер.
- Дисковый бэкенд: virtio-диск на NVMe даёт совсем другой профиль задержек, чем сетевой том. Быстрая прикидка:
dd if=/dev/zero of=/tmp/t bs=1M count=1024 conv=fdatasyncи затем удалить файл. - Флаги CPU: наличие
aesзаметно влияет на скорость TLS-хендшейков, отсутствие — повод удивиться.
У большинства провайдеров, включая Selectel, тип виртуализации и дисковую подсистему видно ещё на этапе выбора конфигурации — сверяйте вывод с описанием тарифа, а не с ожиданиями.
Шаг 2. Первый вход, отдельный пользователь и sudo
Первое подключение почти всегда идёт под root — по паролю из письма или по ключу, добавленному при создании сервера. Работать под root постоянно не нужно: любая опечатка выполняется без подтверждений, а логи не показывают, кто именно что сделал.
# подключаемся первый раз (порт по умолчанию — 22)
ssh root@203.0.113.10
# 1. создаём пользователя: заведёт /home/deploy и спросит пароль
adduser deploy
# 2. выдаём права администратора
# Debian/Ubuntu — группа sudo, RHEL/AlmaLinux/Rocky — wheel
usermod -aG sudo deploy
# 3. переносим авторизованные ключи root в профиль пользователя
rsync --archive --chown=deploy:deploy /root/.ssh /home/deploy/
# 4. права обязаны быть строгими, иначе sshd проигнорирует ключи
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
# 5. ПРОВЕРКА из нового окна терминала, старую сессию не закрывать
ssh -t deploy@203.0.113.10 'id; sudo true && echo sudo-ok'
# 6. и только после успешной проверки блокируем пароль root
passwd -l root
Никогда не закрывайте текущую SSH-сессию, пока не проверили новый доступ из отдельного окна. Это единственная защита от сценария «отключил пароль, ключ не подошёл, консоли у провайдера нет».
SSH-ключи и отключение парольного входа
Пароль подбирается перебором, ключ — нет. Минимальный набор изменений вносится не в основной конфиг, а в drop-in файл, чтобы обновление пакета не затёрло правки:
# /etc/ssh/sshd_config.d/10-hardening.conf
PermitRootLogin prohibit-password
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
MaxAuthTries 3
LoginGraceTime 30
# проверяем синтаксис ДО применения — иначе sshd не поднимется
sshd -t
# перечитываем конфиг без разрыва текущих сессий
systemctl reload ssh
Полный разбор — генерация пары, ed25519 против RSA, ssh-agent, несколько ключей, отладка «Permission denied (publickey)» — в отдельной статье: настройка входа по SSH-ключу. Описание всех директив — в официальном руководстве sshd_config(5).
Смена стандартного порта 22 на нестандартный не является защитой, но заметно уменьшает шум в логах от массовых сканеров. Если меняете — сначала добавьте правило в firewall, потом правьте sshd, и обязательно поправьте профиль ufw.
Шаг 3. Firewall: закрыть периметр до установки софта
На чистой системе firewall обычно пуст: снаружи доступно всё, что слушает на 0.0.0.0. Пока сервисов нет — самое время задать политику «запрещено всё, кроме явно разрешённого».
# политика по умолчанию
ufw default deny incoming
ufw default allow outgoing
# разрешаем только необходимое
ufw allow OpenSSH # профиль = 22/tcp; для другого порта: ufw allow 2222/tcp
ufw allow 80/tcp # HTTP: нужен для редиректа и выпуска сертификата
ufw allow 443/tcp # HTTPS
# страховка от самоблокировки: автосброс правил через 10 минут
systemd-run --unit=ufw-rollback --on-active=10min /usr/sbin/ufw --force reset
ufw enable # запросит подтверждение, предупредит о разрыве SSH
ufw status verbose
# связь жива, правила верны — отменяем страховку
systemctl stop ufw-rollback.timer
Порядок обязателен: сначалаufw allow OpenSSH, только потомufw enable. Обратный порядок — самая частая причина потери доступа к свежему серверу.
Базу данных, Redis, панели администрирования и метрики наружу не выставляют: они слушают на 127.0.0.1 или на приватном интерфейсе. Если доступ нужен извне — только через SSH-туннель или приватную сеть провайдера, но не правилом «разрешить 3306 всем». Что бывает, когда порт всё-таки открыт, разбираем в материале про открытые порты и их риски.
fail2ban одной строкой
Даже при отключённых паролях брутфорс продолжает стучаться и засорять журналы. fail2ban читает логи и банит источники средствами firewall:
apt install -y fail2ban
printf '[sshd]\nenabled = true\nmaxretry = 3\nbantime = 1h\nfindtime = 10m\n' > /etc/fail2ban/jail.d/sshd.local
systemctl restart fail2ban
fail2ban-client status sshd
Тонкости — backend для systemd-журнала, ложные срабатывания, разбор своих логов nginx, whitelist офисных адресов — в подробном руководстве: настройка fail2ban.

Шаг 4. Обновления и автоматические security-патчи
Образ VPS собирается заранее и к моменту выдачи обычно отстаёт на недели. Первое, что делается после закрытия периметра, — полное обновление:
apt update
apt full-upgrade -y
apt autoremove --purge -y
# требуется ли перезагрузка (появляется после обновления ядра/libc)
[ -f /var/run/reboot-required ] && cat /var/run/reboot-required.pkgs
Дальше включается автоматическая установка именно security-обновлений. Полностью автоматические обновления всех пакетов на веб-сервере — риск: мажорная версия PHP или nginx может приехать ночью. Security-ветка гораздо безопаснее и закрывает основную массу CVE.
apt install -y unattended-upgrades apt-listchanges
# включает периодические задания и создаёт 20auto-upgrades
dpkg-reconfigure --priority=low unattended-upgrades
# ключевые параметры в /etc/apt/apt.conf.d/50unattended-upgrades:
# Unattended-Upgrade::Allowed-Origins — оставить только security-источник
# Unattended-Upgrade::Mail "admin@example.com";
# Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
# Unattended-Upgrade::Automatic-Reboot "false";
# Unattended-Upgrade::Automatic-Reboot-Time "04:00";
# сухой прогон: покажет, что именно установилось бы сейчас
unattended-upgrade --dry-run --debug
# проверяем, что таймеры активны и когда сработают
systemctl list-timers apt-daily.timer apt-daily-upgrade.timer
Обновление ядра требует перезагрузки, иначе патч не применён — вы работаете на старом ядре с уязвимостью. Заведите привычку смотреть /var/run/reboot-required и перезагружать сервер в согласованное окно. Справка по параметрам — в документации Debian.
Шаг 5. Hostname, время и NTP
Имя хоста участвует в логах, письмах и заголовках мониторинга. Ставится один раз, в FQDN-формате:
hostnamectl set-hostname web01.example.com
# в /etc/hosts должна быть строка с коротким и полным именем
# 127.0.1.1 web01.example.com web01
hostname -f
Время — недооценённый источник аварий. Перекос часов ломает проверку срока действия TLS-сертификатов, одноразовые коды TOTP, подписи запросов к API облаков и делает журналы бесполезными при разборе инцидента.
# часовой пояс: UTC для сервера удобнее, локальный — для отчётов
timedatectl set-timezone UTC
# статус синхронизации
timedatectl status
# для systemd-timesyncd
systemctl enable --now systemd-timesyncd
timedatectl show-timesync --all | head
# если используется chrony
chronyc tracking
chronyc sources -v
Расхождение часов даже в несколько минут даёт ошибки вида «сертификат ещё не действителен» у части клиентов и ломает двухфакторную авторизацию. Проверяйте синхронизацию сразу, а не когда пользователи начнут жаловаться.
Шаг 6. Swap, лимиты и лишние сервисы
Swap-файл на VPS с малой памятью
На тарифах с 1–2 ГБ памяти swap нужен не как «медленная память», а как подушка: без него ядро при пике убивает процесс через OOM killer, и падает обычно самый жирный — база данных или PHP-FPM. Со swap пик переживается замедлением, а не падением.
# swap уже есть?
swapon --show
free -h
# создаём файл (на ext4 достаточно fallocate; на XFS/btrfs — dd)
fallocate -l 2G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
# закрепляем на перезагрузку
echo '/swapfile none swap sw 0 0' >> /etc/fstab
# уменьшаем агрессивность вытеснения
printf 'vm.swappiness=10\nvm.vfs_cache_pressure=50\n' > /etc/sysctl.d/99-swap.conf
sysctl --system
# проверка
swapon --show
cat /proc/sys/vm/swappiness
Ориентир по размеру: при 1–2 ГБ RAM берут swap примерно равный памяти, при 4–8 ГБ — половину, дальше 4 ГБ хватает почти всегда. На btrfs файл требует отдельной подготовки (без CoW), в контейнерной виртуализации собственный swap может быть недоступен — тогда единственный путь снизить риск OOM — тюнинг лимитов приложения.
Лимиты открытых файлов
Дефолтный лимит дескрипторов легко упирается на связке nginx + PHP-FPM + база под нагрузкой. Симптом — ошибки «too many open files» в логах при внешне свободных ресурсах.
# текущее значение для пользователя
ulimit -n
# для сессий пользователя: /etc/security/limits.d/90-nofile.conf
# * soft nofile 65535
# * hard nofile 65535
# для сервисов systemd лимиты берутся НЕ из limits.conf,
# а из юнита — правится через override
systemctl edit nginx
# [Service]
# LimitNOFILE=65535
systemctl show nginx -p LimitNOFILE
Что торчит наружу
Образы часто несут лишнее: почтовый демон, rpcbind, отладочные сервисы. Каждый слушающий порт — это поверхность атаки.
# кто слушает и от какого процесса
ss -tulpn
# список работающих сервисов
systemctl list-units --type=service --state=running
# отключить и удалить ненужное (пример)
systemctl disable --now rpcbind.socket rpcbind
apt purge -y rpcbind
# журнал не должен съедать диск
journalctl --disk-usage
journalctl --vacuum-time=14d
# постоянный лимит: SystemMaxUse=500M в /etc/systemd/journald.conf

Шаг 7. Веб-стек, SSL, бэкапы и мониторинг — в этом порядке
Базовая часть закончена, сервер можно готовить под проект. Порядок установки имеет значение: каждый следующий слой опирается на предыдущий.
- Веб-сервер. nginx как фронт: TLS, статика, лимиты, проксирование. Конфигурация с нуля разобрана в руководстве по настройке nginx, security-часть — в материале про харденинг веб-сервера.
- Приложение. PHP-FPM, Node.js, Python-воркеры — отдельным пользователем, с сокетом на loopback, без прав записи в каталог с кодом там, где это возможно.
- База данных. Слушает только локальный интерфейс, отдельный пользователь на каждое приложение, пароль не в репозитории.
- SSL. Сертификат выпускается до продакшена, вместе с автопродлением и редиректом с HTTP. Пошагово — в статье про бесплатный сертификат Let's Encrypt.
- Бэкапы. Настраиваются ДО первого деплоя. Схема, проверка восстановления и хранение копий вне сервера — в руководстве по бэкапам сайта.
- Мониторинг. Включается в день запуска: доступность, срок сертификата, время ответа.
Бэкап, который ни разу не разворачивали, бэкапом не является. Проверьте восстановление на этом же чистом сервере, пока на нём нет боевых данных — второго такого удобного момента не будет.
Сводный чек-лист: шаг, команда, зачем и как проверить
| Шаг | Команда или файл | Зачем | Как убедиться, что применилось |
|---|---|---|---|
| Ресурсы и виртуализация | lscpu, free -h, df -hT, systemd-detect-virt | Соответствие тарифу, ограничения контейнера | Вывод совпадает с описанием конфигурации |
| Пользователь с sudo | adduser, usermod -aG sudo | Отказ от постоянной работы под root | ssh -t deploy@host 'sudo true' проходит |
| SSH-ключи | /home/deploy/.ssh/authorized_keys | Вход без пароля, устойчивый к перебору | Вход без запроса пароля из нового окна |
| Отключение паролей | /etc/ssh/sshd_config.d/10-hardening.conf | Снятие класса атак «брутфорс» | sshd -t без ошибок; вход по паролю отклоняется |
| Пароль root | passwd -l root | Учётка root не проходит по паролю | passwd -S root показывает статус L |
| Firewall | ufw allow OpenSSH, ufw enable | Наружу открыто только нужное | ufw status verbose, скан портов снаружи |
| fail2ban | /etc/fail2ban/jail.d/sshd.local | Автобан источников перебора | fail2ban-client status sshd |
| Обновления | apt full-upgrade | Закрытие известных уязвимостей | apt list --upgradable пуст |
| Автопатчи | unattended-upgrades | Security-фиксы приезжают без участия человека | unattended-upgrade --dry-run --debug |
| Hostname | hostnamectl, /etc/hosts | Осмысленные логи и письма | hostname -f даёт FQDN |
| Время | timedatectl, chrony | Валидность TLS, TOTP, корректные журналы | timedatectl status: synchronized = yes |
| Swap | /swapfile, /etc/fstab | Защита от OOM killer на малой памяти | swapon --show после перезагрузки |
| Лимиты | limits.d, LimitNOFILE | Нет ошибок «too many open files» | systemctl show nginx -p LimitNOFILE |
| Лишние сервисы | ss -tulpn, apt purge | Минимальная поверхность атаки | В выводе только ожидаемые порты |
| Журналы | journald.conf, logrotate | Диск не забивается логами | journalctl --disk-usage в пределах лимита |
| SSL и бэкапы | ACME-клиент, скрипт копий | HTTPS и восстановимость с первого дня | Тестовое восстановление прошло |
Как проверить настройку снаружи
Внутренние команды показывают намерение, внешняя проверка — факт. После перезагрузки сервера прогоните его по внешнему контуру:
- Что реально открыто наружу. Сканирование портов с внешнего хоста — проверка открытых портов. Ожидаемый результат: 22 (или ваш SSH-порт), 80, 443 — и ничего больше.
- Сертификат и цепочка. Полнота цепочки, срок действия, протоколы — проверка SSL. Промежуточный сертификат забывают чаще всего: браузер молчит, мобильные клиенты и curl — нет.
- Заголовки безопасности. HSTS, X-Content-Type-Options, политика фреймов — аудит безопасности и построчный разбор в анализаторе HTTP-заголовков.
- Скорость отклика. TTFB со свежего сервера — базовая линия, с которой потом сравнивают деградацию: проверка скорости сайта.
- Дальнейшее наблюдение. Мониторинг доступности с оповещением о падении и об истечении сертификата включается в день запуска, а не после первого инцидента.

Типовые ошибки первого часа
- Включить ufw до разрешения SSH. Классика. Спасает только консоль провайдера.
- Отключить пароли, не проверив ключ. Проверка делается из второго окна до закрытия первого.
- Оставить базу на 0.0.0.0. Даже с паролем это приглашение: боты перебирают учётки MySQL и Redis круглосуточно.
- Забыть про перезагрузку после обновления ядра. Патч установлен, но не применён.
- Настроить бэкапы «после запуска». Первая же ошибка миграции приходится на период без копий.
- Складывать бэкапы на тот же диск. Отказ тома уносит и данные, и копии.
- Ставить панель поверх ручных конфигов. Панель перезапишет nginx и правила firewall, причём молча.
Частые вопросы
Сколько времени реально занимает первичная настройка VPS?
Пункты 0–6 из этого чек-листа — 20–40 минут при наличии готового SSH-ключа. Установка веб-стека, выпуск сертификата и настройка бэкапов добавляют ещё 1–2 часа. Если делаете это регулярно, шаги оформляются в скрипт или Ansible-роль и укладываются в несколько минут.
Нужен ли swap, если памяти достаточно?
Да, небольшой файл полезен даже при запасе: он позволяет ядру вытеснять неиспользуемые страницы и переживать краткие пики без OOM killer. Смысл теряется только при очень агрессивных требованиях к задержкам, где вытеснение недопустимо.
Стоит ли менять SSH-порт с 22?
Это не мера безопасности, а мера снижения шума: массовые сканеры стучатся на 22, и логи чище на нестандартном порту. Целевую атаку смена порта не остановит. Безопасность даёт связка «только ключи + fail2ban + firewall».
Root или sudo — принципиальна ли разница на одиночном сервере?
Да. Отдельный пользователь даёт журналируемость действий, защиту от случайного разрушительного вызова и возможность отозвать доступ одному человеку, не меняя общий пароль. Плюс sshd можно оставить с PermitRootLogin prohibit-password и снять целый класс попыток входа.
Можно ли ставить панель управления после ручной настройки?
Технически да, практически — источник конфликтов: панели переписывают конфигурации веб-сервера, правила firewall и задания cron под себя. Выбирайте один подход: либо панель с самого начала на чистой системе, либо ручное управление.
Как понять, что сервер настроен корректно, а не «вроде работает»?
По внешним признакам: сканирование показывает только ожидаемые порты, сертификат валиден с полной цепочкой, security-заголовки на месте, мониторинг видит сервер, а восстановление из бэкапа реально проверено. Всё остальное — предположения.
Чек-лист перед первым деплоем
- Выбран LTS-дистрибутив, версия зафиксирована в документации проекта.
- Проверены ресурсы, тип виртуализации и свободное место на диске.
- Создан пользователь с sudo, вход по ключу проверен из отдельной сессии.
- Парольная аутентификация отключена, пароль root заблокирован,
sshd -tчист. - Включён firewall с политикой deny incoming; открыты только SSH, 80 и 443.
- Работает fail2ban, jail для SSH активен.
- Система полностью обновлена, unattended-upgrades ставит security-патчи.
- Заданы hostname в FQDN, часовой пояс, синхронизация времени подтверждена.
- Настроен swap и swappiness, лимиты дескрипторов подняты для сервисов.
- Удалены лишние сервисы;
ss -tulpnне содержит сюрпризов. - Ограничен размер журналов, logrotate работает.
- Установлен веб-стек, выпущен сертификат, включён редирект на HTTPS.
- Бэкапы настроены и один раз восстановлены до заливки боевых данных.
- Подключён мониторинг доступности и срока действия сертификата.
- Внешние проверки портов, SSL, заголовков и скорости пройдены после перезагрузки.