Первый час — не опция
Адресное пространство IPv4 в интернете достаточно мало, чтобы просканировать его целиком — задача на несколько минут для обеспеченного ресурсами злоумышленника и на несколько часов для обычного ноутбука. Хостинговые диапазоны опубликованы, каталогизированы и пересканируются постоянно, так что новый адрес — не тайна, а свежая запись в уже существующем списке. На практике первая автоматическая попытка входа по SSH на только что загруженной машине приходит задолго до того, как вы дочитаете приветственное письмо, а следом придут ещё тысячи попыток из никак не связанных между собой источников — и все они перебирают одни и те же несколько сотен паролей для одних и тех же нескольких имён пользователей. Ничего из этого не направлено лично против вас. Это машина, которая сортирует интернет на отвечает и не отвечает, и единственное, что решает, в какую стопку попадёте вы, — это то, что вы сделали в первый час.
Хорошая новость в том, что вся эта работа скучна и конечна. Четыре меры закрывают почти всё: аутентификация по ключам вместо паролей, отказ на каждом входящем порту, который вы сознательно не обслуживаете, применение обновлений безопасности без напоминаний и отказ от работы под root. Каждая занимает несколько минут, и ни одна не экзотична. Отдельно описывать это именно для офшорного no-KYC-сервера стоит из-за асимметрии на другом конце — пути восстановления. На массовом хостинге неудачный sshd_config заканчивается тикетом в поддержку, проверкой личности и сессией на консоли. Здесь нет личности в базе, которую можно было бы проверить, — в этом весь смысл продукта, и по этой же причине неосторожное нажатие клавиши обходится вам в потерянную машину, а не в двадцать минут. Каждый шаг ниже написан с оглядкой на это.
Что на самом деле атакует небольшой сервер
В логах встречаются два совершенно разных типа активности, и их стоит различать, потому что от них защищают разные вещи. Подавляющее большинство — это неизбирательное массовое сканирование: боты, перебирающие адресное пространство в поисках SSH, принимающего пароли, баз данных, привязанных к 0.0.0.0, админ-панелей со стандартными учётными данными, забытых копий staging-окружения, непропатченных веб-приложений с публичным эксплойтом. Этому типу активности всё равно, для чего нужен ваш сервер. С ним нельзя договориться, он никогда не останавливается, и чек-лист из этого руководства побеждает его полностью — не потому что чек-лист хитроумен, а потому что боты ищут машины, которые его пропустили, а таких хватает.
Второй тип — это целевая атака: кто-то хочет добраться именно до вашей машины, — и такое случается редко, обходится дорого и почти никогда не проходит через SSH. Она приходит через развёрнутое вами приложение, непроверенную зависимость, переиспользованный пароль или ноутбук, скомпрометированный ещё до того, как он вообще коснулся сервера. Именно поэтому защита не заканчивается на firewall: настоящие вопросы — это от чьего имени работает ваш сервис, куда он может достучаться наружу и насколько быстро вы накатываете патчи. Стоит знать заранее: наши тарифы VPS — это KVM full virtualization, поэтому вы запускаете собственное ядро, и весь набор инструментов — nftables, ufw, namespaces, seccomp, кастомный sysctl — реально работает, чего нельзя сказать о контейнерных продуктах «VPS», где ядро принадлежит кому-то другому.
SSH: ключи и ровно одна дверь
Аутентификация по паролю на публичном SSH-порту — самый крупный риск, который арендатор сервера создаёт себе сам, и убрать его — самые ценные пять минут во всём этом руководстве. Сгенерируйте пару ключей Ed25519 на своей собственной машине — никогда на сервере, где приватная половина ключа родилась бы прямо на том хосте, который вы пытаетесь защитить, — защитите её парольной фразой и загрузите в агент, чтобы парольная фраза спрашивалась один раз за сессию, а не при каждом входе. Скопируйте публичную половину на сервер, убедитесь, что всё работает, и только после этого отключайте пароли. С PasswordAuthentication no и KbdInteractiveAuthentication no тысячи ежедневных попыток подбора перестают быть риском и становятся просто шумом: угадывать нечего, так что попытка проваливается прежде, чем успевает стать хоть сколько-нибудь интересной.
Root заслуживает отдельного решения. PermitRootLogin prohibit-password сохраняет доступ root по ключу на случай экстренной ситуации; PermitRootLogin no строже и заставляет каждую сессию идти через именованный аккаунт с sudo — то, что нужно в момент, когда к серверу прикасается больше одного человека. Добавьте AllowUsers, чтобы случайный аккаунт, созданный каким-нибудь пакетом, никогда не мог стать путём входа. Перенести демон с порта 22 стоит, но важно честно понимать зачем: это не мера безопасности — кто угодно, сканирующий ваш адрес, найдёт новый порт за секунды, — это гигиена логов: перенос убирает подавляющую часть автоматического шума, так что записи, остающиеся в auth.log, действительно стоит читать. Что бы вы ни меняли, проверяйте синтаксис через sshd -t перед перезагрузкой демона и держите текущую сессию открытой, пока второй терминал не подключится успешно. Эта привычка — вся разница между опечаткой и потерянным сервером.
Default-deny и та половина firewall, которую никто не настраивает
У firewall на сервере одна задача: сделать так, чтобы ответ на вопрос «что здесь слушает?» совпадал со списком того, что вы сознательно опубликовали. Default-deny на входящий трафик, разрешение на исходящий, а дальше открывайте порты по одному, и на каждый должна быть причина. Прежде чем написать хоть одно правило, выполните ss -tulpen и посмотрите, что уже слушает порты, — свежая установка слушает больше, чем большинство ожидает, а база данных или кеш, привязанные к 0.0.0.0 вместо 127.0.0.1, — классический способ, которым небольшой сервер оказывается в чьём-то датасете. Сначала привязывайте локальные сервисы к loopback; firewall — это ваш второй рубеж, а не единственный.
А есть ещё половина, которую обычно пропускают. Каждый тариф здесь включает /64 IPv6 в дополнение к IPv4, и большинство современных демонов с готовностью слушают оба семейства адресов. Если ваши правила покрывают только v4, сервис, который вы считаете закрытым firewall, оказывается доступен по v6 для любого, кто разрешит адрес, — а сканирование IPv6 в пределах известного хостингового префикса давно стало рутиной. ufw действительно умеет работать с обоими семействами, но только когда в /etc/default/ufw установлено IPV6=yes; проверяйте это через ufw status verbose, а не полагайтесь на предположения. Docker заслуживает такого же недоверия: публикация порта контейнера через -p добавляет правила в собственную цепочку iptables, которая обрабатывается раньше правил ufw, так что контейнер, который вы считали защищённым, часто оказывается полностью открыт. Привязывайте его явно к 127.0.0.1:port и ставьте перед ним reverse proxy. Что бы вы ни настроили, проверяйте это с другой точки в интернете — правило, которое только читали, — это правило, которое никогда не тестировали.
Обновления, о которых не нужно помнить
Непропатченный софт — вот из-за чего на самом деле теряется большинство небольших серверов, и причина здесь человеческая, а не техническая: накатывать патчи — рутинная обязанность, которая конкурирует за ваше время со всем остальным, что нужно сделать. Автоматизируйте канал безопасности — и проблема исчезает. unattended-upgrades в Debian и Ubuntu применяет обновления безопасности по расписанию и не мешается под ногами; ограничьте его именно security-веткой, а не всеми доступными обновлениями, чтобы обычный релиз с новыми фичами никогда не перезагружал ваше приложение в три часа ночи. Это важнее для офшорной машины, которую вы администрируете самостоятельно, чем для управляемой платформы: патчить её за вас некому, и никакой персональный менеджер не напишет вам о критической CVE — в базе просто нет email-адреса, на который можно написать.
Обновления ядра требуют перезагрузки, чтобы вступить в силу, так что политику перезагрузок стоит выбрать осознанно, а не узнавать о ней постфактум. needrestart покажет, какие сервисы всё ещё работают со старыми, уже удалёнными библиотеками, а окно Unattended-Upgrade::Automatic-Reboot в глухие ночные часы вполне подходит для stateless-сервиса. Есть важный нюанс: если вы прошли наше руководство по полнодисковому шифрованию и ваша корневая файловая система зашифрована, автоматическая перезагрузка остановится на запросе парольной фразы и будет ждать там, пока вы не разблокируете диск по сети. Либо держите автоматические перезагрузки выключенными на таких машинах, либо убедитесь, что удалённая разблокировка протестирована и вы не спите в момент этого окна. Что бы вы ни выбрали, запишите это — политика, которая существует только у вас в голове, перестаёт существовать в тот момент, когда вы уезжаете в отпуск.
Ограничение скорости, fail2ban и уровень шума
Как только аутентификация по паролю отключена, брутфорс против SSH обречён на провал. Стоит честно сказать, что после этого момента дают инструменты вроде fail2ban: не защиту от подбора — она и так уже невозможна, — а более тихий лог, меньше процессорного времени, потраченного на заведомо обречённые рукопожатия, и по-настоящему рабочий контроль на тех уровнях, где секреты всё ещё можно угадать. Направьте его туда, где это действительно важно: форма входа веб-приложения, SMTP AUTH почтового сервера, admin-путь, который кто-то перебирает. ufw limit даёт дешёвое ограничение частоты подключений вообще без дополнительного софта. Ещё пяти минут стоит горстка настроек ядра — включить SYN cookies, включить reverse-path filtering, выключить ответы на broadcast ICMP-запросы и игнорировать анонсы IPv6-роутера на сервере со статическим адресом.
Но важно понимать, где заканчиваются возможности хоста. Ничего из этого не переживает объёмную атаку, потому что флуд забивает сетевой канал задолго до того, как начинает беспокоить процессор: к моменту, когда пакеты доходят до ваших правил nftables, они уже съели ту полосу пропускания, которую вы пытались защитить. Именно поэтому L3/L4-фильтрация мощностью до 1.5 Tbps стоит выше по потоку от всего флота и включена в каждый тариф, а не продаётся как опция, — с дополнительной L7-защитой от флуда на уровне приложений, который выглядит как легитимные запросы. Firewall на хосте отвечает за точность; сеть отвечает за объём. Когда одно пытаются подменить другим, результатом обычно становится неприятный сюрприз.
Настоящий риск здесь — заблокировать себе доступ
Из всего, что может пойти не так в этот первый час, самый вероятный исход — вовсе не взлом. Это вы сами, в конце долгой сессии, перезагружаете сломанный sshd_config или включаете firewall, в разрешающем правиле которого затесалась опечатка, — и обнаруживаете, что дверь захлопнулась за вами. На массовом хостинге это просто досадная неприятность. Здесь к этому стоит отнестись по-настоящему серьёзно, потому что регистрация здесь — это логин и пароль, восемь кодов восстановления и никакого email во всей цепочке, именно для того, чтобы утекать было нечему, — а провайдер, который не может опознать вас, точно так же не может опознать вас, чтобы вернуть вам доступ к вашему серверу. Никто не может поручиться за вас. Это свойство — продукт, работающий именно так, как задуман, и именно поэтому меры предосторожности ниже — это привычки, а не рекомендации.
Четыре из них не стоят ничего. Сделайте снапшот прежде, чем трогать SSH или firewall, чтобы неудачная перезагрузка демона обернулась откатом, а не пересборкой с нуля. Добавьте второй публичный ключ с другого устройства — телефона, рабочего ноутбука, офлайн-бэкапа, — потому что один ключ на одном диске отделяет вас от полного отсутствия ключей ровно на одну пролитую чашку кофе. Держите второй терминал подключённым, пока меняете что угодно, способное оборвать первый, и всегда проверяйте новую конфигурацию с нового подключения, прежде чем отпускать старое. И примите, на чём заканчивается лестница восстановления: снапшоты живут на том же хосте и представляют собой удобство отката, а не бэкап, так что всё, что вам будет по-настоящему жаль потерять, должно храниться вне хоста — в надстройке ежедневных зашифрованных бэкапов или в ваших собственных зашифрованных на стороне клиента копиях, отправленных куда-то ещё. Ваш худший случай должен выглядеть как повторное развёртывание и восстановление, занимающее минуты, а не как всё пропало.
SP·08Что добавить дальше — в зависимости от роли машины
Чек-лист выше — это пол, и он одинаков для любой машины. Что строится поверх него, целиком зависит от задачи. Публичному веб-серверу нужны TLS, reverse proxy, который его терминирует, приложение, работающее от имени непривилегированного пользователя, и база данных на loopback или вовсе на отдельной машине. VPN-точка устроена иначе — один UDP-порт, никакого веб-стека, вообще никаких публичных сервисов, — и это подробно разобрано в материале про то, как поднять собственный VPN на WireGuard. Почтовый сервер — самый требовательный из трёх и нуждается в контроле rDNS-записи для адреса, что включено в каждый тариф; половину, отвечающую за доставляемость писем, разбирает материал про самостоятельный почтовый сервер на офшорном VPS. Tor-релей нарочно устроен противоположно анонимности — это опубликованный, доступный для связи сервис, — и компромиссы этого решения изложены в материале про запуск релея на no-KYC VPS. Всё, что хранит приватное состояние в состоянии покоя, тоже стоит зашифровать — это отдельная мера со своими сценариями отказа, разобранная в материале про LUKS и удалённую разблокировку.
Стоит закончить тем, какое место защита занимает в общей картине, — ведь это лишь один из трёх уровней, и каждый из них отказывает независимо от остальных. Первый — это то, что о вас знает хостер, и здесь это почти ничего: логин, пополняемый предоплаченным крипто-балансом от $30.00, без карты и без документа во всей цепочке — этому посвящена статья анонимная оплата хостинга. Второй — это то, чьё право применяется, что определяется тем, где физически находится железо среди наших 6 регионов, а не тем, где находитесь вы, — этот вопрос разбирается регион за регионом в материале какую офшорную локацию выбрать?. Третий — это то, что позволяет сама машина, и здесь всё зависит только от вас — ни один провайдер не настроит это за вас. VPS от $8.00/мес выходит в онлайн примерно за 15 min, а значит, именно следующий час решает, насколько эта машина реально защищена. Потратьте его осознанно.
SP·09Шаг за шагом
-
01
Разверните сервер и зайдите на него прежде всего остального
Разверните сервер из панели управления и выберите дистрибутив, который вы реально будете патчить, — актуальный Debian или Ubuntu LTS здесь скучный, но правильный ответ. VPS выходит в онлайн примерно за 15 min, а данные для входа под root появляются в панели. Подключайтесь сразу же, полностью обновите систему и задайте hostname, чтобы логи потом можно было читать. На этой машине больше ничего не происходит, пока не выполнены остальные шаги.
ssh root@203.0.113.10 apt update && apt full-upgrade -y hostnamectl set-hostname edge-01 apt install -y ufw unattended-upgrades needrestart
-
02
Уйдите от root: именованный пользователь, ключ и sudo
Сгенерируйте пару ключей на своём ноутбуке, никогда на сервере. Затем создайте на машине именованный аккаунт, поместите публичную половину ключа в его
authorized_keysи выдайте емуsudo. Откройте второй терминал и войдите под этим пользователем прямо сейчас — прежде чем менять что-либо ещё, пока вход root по SSH всё ещё работает как запасной вариант.# on your own machine ssh-keygen -t ed25519 -C "laptop" ssh-copy-id -i ~/.ssh/id_ed25519.pub root@203.0.113.10 # on the server adduser --disabled-password --gecos "" ops usermod -aG sudo ops install -d -m 700 -o ops -g ops /home/ops/.ssh cp /root/.ssh/authorized_keys /home/ops/.ssh/ chown ops:ops /home/ops/.ssh/authorized_keys chmod 600 /home/ops/.ssh/authorized_keys
-
03
Ужесточите демон SSH — со вторым открытым терминалом
Записывайте изменения в drop-in-файл, а не редактируйте штатный конфиг, — тогда обновление дистрибутива никогда молча их не откатит. Проверьте синтаксис через
sshd -tперед перезагрузкой демона, перезагрузите его, а затем подтвердите работоспособность через совершенно новое подключение, пока старое ещё живо. Если новое подключение не удастся, у вас всё ещё останется рабочая сессия, чтобы всё отменить. Одна ловушка, специфичная для дистрибутива: в Ubuntu 24.04 демон активируется через socket, так чтоPortвsshd_configигнорируется, — задавайте порт черезsystemctl edit ssh.socket, либо отключите socket-юнит.cat > /etc/ssh/sshd_config.d/10-hardening.conf <<'EOF' Port 2222 PermitRootLogin prohibit-password PasswordAuthentication no KbdInteractiveAuthentication no AllowUsers ops X11Forwarding no MaxAuthTries 3 LoginGraceTime 20 EOF sshd -t && systemctl reload ssh # new terminal, do not close the old one: ssh -p 2222 ops@203.0.113.10
-
04
Настройте default-deny на firewall для обоих семейств IP
Убедитесь, что IPv6 включён в ufw, затем запретите весь входящий трафик и откройте только те порты, которые сознательно обслуживаете, — включая новый порт SSH, который нужно разрешить до включения firewall. Заодно проверьте, что реально слушает система, и переведите на loopback всё, что должно оставаться локальным.
grep IPV6 /etc/default/ufw # must read IPV6=yes ufw default deny incoming ufw default allow outgoing ufw limit 2222/tcp ufw allow 80,443/tcp ufw enable ufw status verbose ss -tulpen # anything on 0.0.0.0 or :: ?
-
05
Включите автоматические обновления безопасности
Включите только security-канал, чтобы патчи прилетали без внезапного перезапуска приложения из-за обновления с новыми фичами. Явно определите политику перезагрузок — и оставьте автоматические перезагрузки выключенными, если корневая файловая система зашифрована и требует ручной разблокировки.
dpkg-reconfigure -plow unattended-upgrades cat > /etc/apt/apt.conf.d/51-local <<'EOF' Unattended-Upgrade::Automatic-Reboot "false"; Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; EOF unattended-upgrade --dry-run --debug | tail -20
-
06
Уберите шум и подтяните настройки ядра
Добавьте
fail2banдля тех уровней, где секрет всё ещё можно угадать, и задайте ту небольшую горстку значений sysctl, которая попросту корректна для публичного сервера со статическим адресом. Это дёшево и делает логи достойными прочтения.apt install -y fail2ban cat > /etc/sysctl.d/99-harden.conf <<'EOF' net.ipv4.tcp_syncookies = 1 net.ipv4.conf.all.rp_filter = 1 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.conf.all.accept_redirects = 0 net.ipv6.conf.all.accept_ra = 0 kernel.kptr_restrict = 2 EOF sysctl --system
-
07
Проверьте снаружи, затем запишите runbook
Доверяйте виду снаружи, из интернета, а не виду изнутри машины. Убедитесь, что старый порт SSH больше не отвечает, что ничего неожиданного не откликается и что вход только по ключу действительно принудительно включён. Затем запишите три факта, которые понадобятся вам в ваш худший день: где хранится второй ключ, какой снапшот вы делали и где лежит бэкап вне хоста.
# from another machine ssh -p 22 ops@203.0.113.10 # must time out or be refused ssh -p 2222 -o PreferredAuthentications=password ops@203.0.113.10 # expected: Permission denied (publickey) ssh -p 2222 ops@203.0.113.10 'sudo ufw status verbose; uptime'


