Для начинающего админа (или программиста, пошагово повторяющего по гайду известного ютубера покупку VPS на популярном сервисе) настройка Linux-сервера может показаться чёрным колдунством или просто тарабарщиной. «Работает же… как-то...». Вот только на самом деле, всё не так просто, как бы хотелось.
В недавнем исследовании "Tunneling into the Unknown" отмечено, что из 4 000 протестированных туннелей ⚠️ 3 211 (80,3 %) предоставляют доступ по SSH на стандартном порту 22, причём по конверсии и забивчивости (уже моё предположение) с возможностью входа под root и без элементарных ограничений будет 3000+ точно

Это как если бы вы не просто не закрывали на ночь входную дверь в многоквартирном доме, а оставили её настежь и с неоновыми стрелочками, начиная от входа в парадную до нужной двери.
Даже если у вас один-единственный сервер, где крутится телеграм-бот, показывающий самый просматриваемый за день пост с пинтереста, с ним прежде всего необходимо провести ряд базовых взаимодействий, чтобы увеличить собственные шансы остаться в тени от перебора логина и пароля чьей-нибудь ботнет фермы... прежде чем оставить его пылиться до деплоя следующего супер важного и интересного телеграм бота. И в этой статье я планирую поделится с вами собственными скриптами, многократно использующимися на реальных продовых системах.

⚠️ Предварительное замечание: Автор не претендует на роль истины в последней инстанции по абсолютной защите сервера. Любой сервер — это, в каком-то смысле, дырявый дуршлаг, где невозможно закрыть все отверстия макаронами. Но, если следовать простым советам из этого руководства, можно быть уверенным хотя бы в том, что ваш сервер не пустит к себе первого попавшегося прохожего «с полпинка».
⚠️ Второе предварительное замечание: Эта статья рассчитана на среднестатистический VPS-сервер, который арендуется у популярного хостера «на попробовать», «для проектов», «под бота» или «под VPN». Чаще всего такие серверы поставляются с Ubuntu просто потому, что она первой в списке при создании инстанса. По разным источникам, Ubuntu стабильно занимает 30–50% рынка серверных Linux-дистрибутивов, и потому все примеры и команды здесь ориентированы именно на неё — не потому что это единственно правильный выбор, а потому что это дефолт большинства провайдеров, а значит, и главный кандидат на статью «как сделать безопаснее».
1. Первый вход
И вот настаёт тот момент, когда мы, полные надежд, радости и весеннего обострения, получаем от провайдера с пылу с жару новую VPS. Мол, получите и распишитесь: адрес 123.45.67.89, пользователь root, и пароль — какой-нибудь забористый, явно не похожий на Q!w2e3r4t5y6 (хотя, как говорится, видел я и не такое).
Большинство на этом этапе не теряют ни секунды и сразу же вводят в терминал (или PuTTY):
ssh root@123.45.67.89
а затем вставляют ту самую забористую строку, которая пароль, загружают папку с кодом через какой-нибудь WinSCP, и довольные жизнью считают, что они уже победили. Продвинутые неофиты иногда добавляют здесь ещё и SSH-ключ, чтобы удобно делать git pull из приватного репозитория на GitHub и не мучиться с ручным переносом файлов. Но это, как говорится, уже совсем другая история…Однако есть ряд базовых шагов, которые помогут вам не столкнуться с последствиями собственной беспечности уже через несколько дней.

1.1 Обновление системы
Первое и самое простое, что нужно сделать — установить последние патчи. Даже если образ, который вы выбрали, относительно свежий, обновления никогда не бывают лишними:
apt update && apt upgrade -y
1.2 Удаляем заведомо ненужные сетевые сервисы
telnetd, rsh, tftp и прочие технологии, конечно, классные, сыграли роль в становлении интернета... Но они были созданы в эпоху, когда шифрование считалось забавой параноиков, а не стандартом безопасности. Им не место на вашей машине:
apt --purge remove -y xinetd nis yp-tools tftpd atftpd tftpd-hpa telnetd rsh-server rsh-redone-server
1.3 Включаем базовый firewall
Если объяснить популярным языком: фаервол — это очень важная консьержка. Вы держите её в курсе, кому и в какую квартиру (порт) можно заходить снаружи.
Мы же не хотим, чтобы любой нетсталкер, шарящийся по случайным IP адресам знал, что у нас на сервере доступна база данных PostgreSQL на порту 5432, верно? Из личного опыта могу рассказать, что если этого НЕ сделать, то однажды вы можете обнаружить, как на вашей машине уютно устроился майнер, пробравшийся через какую-нибудь уязвимость в MySQL/MariaDB.
Программы внутри сервера по-прежнему смогут общаться между собой. Но для внешнего мира будет видно только то, что вы явно разрешили. На этом этапе разрешаем только SSH (стандартный порт 22):
ufw allow 22 # Ну или можно вместо 22 ssh
ufw default deny incoming
ufw default allow outgoing
ufw enable
Небольшая ремарка: если у вас какое-то веб-приложение, которую, бы, желательно оставить доступной для всего мира, то нужно нашей консьерже про это сказать, иначе оно "перестанет работать".
ufw allow 80 # или просто "http"
ufw allow 443 # или просто "https"
1.4 (Опционально) Не забываем и про личное удобство
Следующее, что я делаю — ставлю любимый редактор и оболочку. Это не про безопасность, это про не сойти с ума от nano и скучного Bash. Если у вас другие предпочтения — нормально, но раз уж вы здесь, — делюсь тем, что сам использую: neovim и fish.
Обратите внимание: последующие команды ориентированы на fish. Если вы остались в bash, придётся немного адаптировать синтаксис:
apt install -y neovim fish
chsh -s /bin/fish $USER # Включаем fish для последующих входов под этим пользователем
fish
Подведём маленькие итоги и отойдём на небольшую разминку
На этом этапе у нас уже обновлённая система, безопасный минимум установленных пакетов, настроенный фаервол, чуть больше удобства для работы.
Самое время сделать паузу, налить чаю и позволить себе пару спокойных вдохов. Сервер ещё не крепость, но уже не картонная коробка.
Ещё раз, одним блоком весь пулл команд из первого раздела:
Скрытый текст
apt update && apt upgrade -y
apt --purge remove -y xinetd nis yp-tools tftpd atftpd tftpd-hpa telnetd rsh-server rsh-redone-server
ufw allow 22
#ufw allow 80 # или просто "http"
#ufw allow 443 # или просто "https"
ufw default deny incoming
ufw default allow outgoing
ufw enable
apt install -y neovim fish ssh
chsh -s /bin/fish $USER
fish
⚠️ Важно, если у вас установлена панель управления (ISPmanager, HestiaCP, VestaCP, aaPanel и пр.):
Большинство панелей используют определённые порты (например, 1500, 8083, 8888 и т. д.) и могут завязаться на стандартный 22-й порт SSH.
Перед тем как что-либо менять в файрволе или переносить SSH на нестандартный порт, обязательно проверьте документацию вашей панели:
– какие порты она использует,
– не сломается ли она при смене SSH-порта,
– как восстановить доступ, если что-то пойдёт не так.
В противном случае вы рискуете остаться без доступа как к серверу, так и к самой панели.
2. А кто, если не root?
Если вы откроете похожие статьи на Хабре (или где-нибудь ещё), буквально в каждой увидите совет создать отдельного пользователя, а вход по root обрубить, так сказать, с КоРнЯмИ. Так вот, эта статья не исключение.

2.1 Пока отвлечёмся от сервера и откроем новую сессию терминала
Сначала придумаем имя для нашего пользователя и сгенерируем для него пару SSH-ключей (приватный и публичный), по которой в последствии будем заходить. Кто-то использует свой универсальный никнейм, кто-то берёт случайную строку из 8-12 символов и считает, что так безопасно. Этот интимный момент оставим на ваше усмотрение. Для наглядности я воспользуюсь генератором, а вы по желанию, но заполните эти 6 строк по ситуации:
set ENTRY_USER (openssl rand -base64 12) # получилось OkNVvOpNNjZuu9B2
set ENTRY_NAME next_host # Имя, по которому мы в дальнешем будем входить на наш сервер
set ENTRY_HOST 123.45.67.89
set ENTRY_PORT (math (random 1024 65535))
set ENTRY_FILE "~/.ssh/securitykey"
Теперь подготовим удобную конфигурацию для входа:
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "
Host $ENTRY_NAME
HostName $ENTRY_HOST
Port $ENTRY_PORT
User $ENTRY_USER
IdentityFile $ENTRY_FILE
" >> ~/.ssh/config
chmod 600 ~/.ssh/config
ssh-keygen -t ed25519 -a 100 -N "" -f $ENTRY_FILE
clear && clear
echo "set CREATED_USER $ENTRY_USER"
echo "set CREATED_USER_PASSWORD $(openssl rand -base64 32)"
echo "set USER_AUTH_KEY "(cat "$ENTRY_FILE.pub")
echo "set new_port $ENTRY_PORT"
echo "set sshd_config /etc/ssh/sshd_config.d"
Вы получите на выходе набор команд, которые вскоре понадобятся на сервере. Советую сохранить этот вывод в надёжном месте — хотя бы в блокноте (но лучше в KeePassXC). Если вы вдруг забыли, как копировать из терминала Linux, напоминаю: Ctrl+Shift+C, вставить — Ctrl+Shift+V. Скоро станет ясно, зачем нам всё это.
2.2 Возвращаемся к нашему серверу, самое время вспомнить как он тут поживает
Помните вывод из предыдущего шага? Надеюсь, вы его скопировали. Если да — вставляйте прямо в текущий терминал сервера. Там должно быть примерно так:
set CREATED_USER OkNVvOpNNjZuu9B2
set CREATED_USER_PASSWORD pOKh7n61ww6WG74Ke9II6aRHATQmv2vrM57UgS14e/M=
set USER_AUTH_KEY ssh-ed25519 AAAAB3N...gd+jQ== oh-my-way
set new_port 54426
set sshd_config /etc/ssh/sshd_config
Эти переменные помогут понадобятся нам чуть чуть попозже...
2.3 Создадим нашего пользователя на сервере
Пришло время непосредственно создать нашего пользователя, добавить его в sudo-группу, положить к нему наш публичный ключ и передать ему права на его папку с ключами:
# Создаём пользователя с домашней директорией и shell fish, задаём пароль
sudo useradd -m -s /bin/fish $CREATED_USER echo "$CREATED_USER:$CREATED_USER_PASSWORD" | sudo chpasswd
sudo usermod -aG sudo $CREATED_USER
sudo mkdir -p /home/$CREATED_USER/.ssh
sudo echo "$USER_AUTH_KEY" | sudo tee /home/$CREATED_USER/.ssh/authorized_keys
sudo chown -R $CREATED_USER:$CREATED_USER /home/$CREATED_USER/.ssh
sudo chmod 700 /home/$CREATED_USER/.ssh
sudo chmod 600 /home/$CREATED_USER/.ssh/authorized_keys
2.4 Меняем порт и говорим ssh.socket: «спасибо, до свидания»
Теперь наш новый пользователь уже есть, ключи на месте. Остался последний аккорд: сменить замок на двери, чтобы больше никто не заходил через старую.
По умолчанию Ubuntu запускает SSH не напрямую, а по принципу «по требованию» — через ssh.socket. Это вроде как экономит ресурсы, но есть нюанс: при экстренном обновлении OpenSSH (например, из-за критической уязвимости) эта схема может не перезапустить нужную версию демона, и вы останетесь с дырявым замком, хоть и красивым. И из личного опыта сокет не очень то ладит с конфигами ссшд, а может это просто я не до конца разобрался как это всё настроить по-человечески.
Так что мы сделаем по-старинке: отключим эту «умную» систему и будем запускать sshd как нормальный сервис. Заодно и порт сменим, и вход по root отключим. Всё просто:
mkdir -p /etc/systemd/system-generators/
ln -s /dev/null /etc/systemd/system-generators/sshd-socket-generator
sudo systemctl daemon-reload
sudo systemctl disable --now ssh.socket
sudo systemctl enable --now ssh.service
Теперь sshd будет жить как все приличные сервисы, без фокусов с ожиданием подключения.
А вот и вторая часть — настраиваем новый порт и закрываем вход по паролю и root:
echo "Port $new_port
PasswordAuthentication no
PermitRootLogin no
" | sudo tee /etc/ssh/sshd_config.d/99-custom.conf > /dev/null
И пока не будем рестартить службу, там есть то, что в следующем разделе обыграем по-другому
2.5 Обновляем UFW и проверяем доступ
На этом этапе сервер уже не напоминает проходной двор. Но раз уж мы начали делать «как надо», есть пара вещей, о которых стоит задуматься чуть глубже, чем просто смена порта.
По умолчанию мы включили ufw — и для большинства сценариев этого достаточно. Но к чему нам обрубки функционала, когда мы можем править миром всем интернетом ввереной нам машины.
sudo apt install nftables
sudo nft list ruleset
Как и всё былое, к чему мы уже успели дотронутся, не забываем выстроить минимальный конфиг для новозакаченной службы
echo "table inet filter {
chain input {
type filter hook input priority 0;
iif lo accept
ct state established,related accept
tcp dport $new_port accept
# tcp dport 80 accept
# tcp dport 443 accept
reject with icmpx type port-unreachable
}
}" | sudo tee /etc/nftables.conf > /dev/null
Ммм-м, красота! пора поменять ту дефолтную коньсержку на полноценную домофонную дверь и перезагрузить sshd:
sudo systemctl disable --now ufw
sudo systemctl enable --now nftables
sudo systemctl restart ssh
Теперь очень важный момент: откройте новое локальное окно терминала и введите:
ssh $ENTRY_NAME
Пустило под новым пользователем? Славно! Значит, вы сделали всё правильно. Старую сессию (root) теперь можно закрывать — она больше не нужна.
Кстати, в последствии под созданным пользователем на linux-машине при исполнении какой-либо команды, начинающейся с sudo, система с вас потребует пароль именно этого пользователя, а не от root, под которым мы входили в первый раз
Итоги второго раздела
Теперь сервер уже не безликий набор байтов с доступом по root, а вполне себе защищённая рабочая машина. Мы отключили вход по root и паролям, сменили стандартный SSH-порт, создали отдельного пользователя с правами sudo и доступом по ключу. Ваш сервер теперь в числе 19,7% машин, на которые не попадёт случайный бот, перебирающий стандартные порты и пароли.
Если вы сделали всё это — поздравляю, вы уже значительно подняли безопасность своего сервера. Дальше останется поставить пару автоматических «охранников», чтобы отбиваться от самых настойчивых гостей.
Ещё раз, объединёнными блоками весь пулл команд из второго раздела:
Скрытый текст
# ЛОКАЛЬНО
set ENTRY_USER (openssl rand -base64 12) # получилось OkNVvOpNNjZuu9B2
set ENTRY_NAME next_host # Имя, по которому мы в дальнешем будем входить на наш сервер
set ENTRY_HOST 123.45.67.89
set ENTRY_PORT (math (random 1024 65535))
set ENTRY_FILE "~/.ssh/securitykey"
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "
Host $ENTRY_NAME
HostName $ENTRY_HOST
Port $ENTRY_PORT
User $ENTRY_USER
IdentityFile $ENTRY_FILE
" >> ~/.ssh/config
chmod 600 ~/.ssh/config
ssh-keygen -t ed25519 -b 4096 -N "" -f $ENTRY_FILE
clear && clear
echo "set CREATED_USER $ENTRY_USER"
echo "set CREATED_USER_PASSWORD $(openssl rand -base64 32)"
echo "set USER_AUTH_KEY "(cat "$ENTRY_FILE.pub")
echo "set new_port $ENTRY_PORT"
echo "set sshd_config /etc/ssh/sshd_config.d"
# СЕРВЕР
sudo useradd -m -s /bin/fish $CREATED_USER
echo "$CREATED_USER:$CREATED_USER_PASSWORD" | sudo chpasswd
sudo usermod -aG sudo $CREATED_USER
sudo mkdir -p /home/$CREATED_USER/.ssh
sudo echo "$USER_AUTH_KEY" | sudo tee /home/$CREATED_USER/.ssh/authorized_keys
sudo chown -R $CREATED_USER:$CREATED_USER /home/$CREATED_USER/.ssh
sudo chmod 700 /home/$CREATED_USER/.ssh
sudo chmod 600 /home/$CREATED_USER/.ssh/authorized_keys
mkdir -p /etc/systemd/system-generators/
ln -s /dev/null /etc/systemd/system-generators/sshd-socket-generator
systemctl daemon-reload
systemctl disable --now ssh.socket
systemctl enable --now ssh.service
sudo mkdir -p /etc/ssh/sshd_config.d
echo "Port $new_port
PasswordAuthentication no
PermitRootLogin no
" | sudo tee /etc/ssh/sshd_config.d/99-custom.conf > /dev/null
sudo apt install nftables
echo "table inet filter {
chain input {
type filter hook input priority 0;
iif lo accept
ct state established,related accept
tcp dport $new_port accept
# tcp dport 80 accept
# tcp dport 443 accept
reject with icmpx type port-unreachable
}
}" | sudo tee /etc/nftables.conf > /dev/null
sudo systemctl disable --now ufw
sudo systemctl enable --now nftables
sudo systemctl restart ssh
# ЛОКАЛЬНО
ssh next_host
3. Кто стучится в дверь мою?

Теперь, когда у нас есть отдельный пользователь, нестандартный порт SSH и закрыт доступ по root, мы уже ощутимо усложнили жизнь ботам и случайным хулиганам. Поставим парочку автоматических вышибал у дверей нашего сервера, которые не дадут расслабиться даже самым настойчивым.
3.1 Fail2ban
Китайцы взломали сервер Пентагона, вот как это было:
Каждый китаец попробовал один пароль.
Каждый второй пароль был "Мао Цзедун"
74357181-й попытке- сервер согласился, что у него пароль "Мао Цзедун"
Fail2ban — это очень простой, но крайне полезный инструмент, следящий за событиями в логах. И если кто-то слишком часто пытается угадать пароль (особенно от root, которого у нас уже нет), — он просто выписывает бан по IP.
Чтобы не ковыряться в конфиге вручную — настроим его как положено, автоматически:
sudo apt install -y fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
echo '
[sshd]
enabled = true
port = $new_port
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
' | sudo tee -a /etc/fail2ban/jail.local > /dev/null
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
3.2 Portsentry
А его можно сравнить с той самой беспокойной бабушкой на лавочке у подъезда с завышенным чувством гражданского долга «А хто тут по окошкам всё высматривает? чёйта??»
Как только Portsentry замечает, что кто-то начинает простукивать все подряд порты, он автоматически блокирует IP нарушителя.
sudo apt install -y portsentry
set portsentry_conf /etc/portsentry/portsentry.conf
sudo cp $portsentry_conf $portsentry_conf.bak
sudo awk '
/^[[:space:]]*BLOCK_UDP[[:space:]]*=/ { print "BLOCK_UDP=\"1\""; next }
/^[[:space:]]*BLOCK_TCP[[:space:]]*=/ { print "BLOCK_TCP=\"1\""; next }
/^[[:space:]]*KILL_ROUTE[[:space:]]*=/ { print "KILL_ROUTE=\"/bin/echo '\''portsentry: attack from host: $TARGET$ on port: $PORT$'\'' >> /var/log/portsentry.log\""; next }
/^[[:space:]]*TCP_MODE[[:space:]]*=/ { print "TCP_MODE=\"atcp\""; next }
/^[[:space:]]*UDP_MODE[[:space:]]*=/ { print "UDP_MODE=\"audp\""; next }
{ print }
' $portsentry_conf.bak | sudo tee $portsentry_conf > /dev/null
sudo systemctl enable --now portsentry
echo '[Definition]
failregex = portsentry: attack from host: <HOST> on port: \d+
ignoreregex =' | sudo tee /etc/fail2ban/filter.d/portsentry.conf > /dev/null
echo '
[portsentry]
enabled = true
filter = portsentry
logpath = /var/log/portsentry.log
bantime = 86400
maxretry = 2
' | sudo tee -a /etc/fail2ban/jail.local > /dev/null
sudo systemctl restart fail2ban
Действует весь механизм следующим образом:
В момент, когда кто-то начинает подозрительно простукивать порты вашего сервера — как школьник, идущий вдоль подъезда и дёргающий ручки всех дверей — Portsentry, наша бдительная бабка с лавочки, тут же замечает:
«Ага, этот вон уже к седьмой двери подряд подошёл!»
Она ничего не делает сама — не бьёт метлой, не бросается тапками — она просто записывает в свой журнал наблюдений (/var/log/portsentry.log), что за IP, с какого порта и в каком времени был замечен.
А уже дальше в дело вступает Fail2ban. Он, как сотрудник ЖЭКа с полномочиями и доступом к iptables, читает журнал бабушки, смотрит:
«Ага, третий раз уже этот тип светится...»
— и без лишней бюрократии закрывает перед нарушителем дверь на 24 часа.
Сработает даже на роботов и сканеры, которым бабушка не нравится ни визуально, ни логически — не важно, это просто эффективно.
Итоги 3 раздела
Ну и по итогу у нас уже сервер самостоятельно отгоняется от подозрительных операций (главное в такой защите не влипнуть самому), и на самом деле, далеко не все вещи, и технологии самозащиты проделаны... например,вы знали что можно подключить TOTP при входе на пользователя? а то что можно не держать наружу наш порт для подключения ssh, а открывать его только для нашего ip, если «постучаться» в правильную последовательность других портов? Это всё бы можно было сделать, но в рамках разумной паранойи выглядит слишком вычурно и больше себе вставляем палок в колёса, но об этом, может быть, потом.
Все команды этого раздела в одном блоке:
Скрытый текст
sudo apt install -y fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
echo '
[sshd]
enabled = true
port = $new_port
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
' | sudo tee -a /etc/fail2ban/jail.local > /dev/null
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
sudo apt install -y portsentry
set portsentry_conf /etc/portsentry/portsentry.conf
sudo cp $portsentry_conf $portsentry_conf.bak
sudo awk '
/^[[:space:]]*BLOCK_UDP[[:space:]]*=/ { print "BLOCK_UDP=\"1\""; next }
/^[[:space:]]*BLOCK_TCP[[:space:]]*=/ { print "BLOCK_TCP=\"1\""; next }
/^[[:space:]]*KILL_ROUTE[[:space:]]*=/ { print "KILL_ROUTE=\"/bin/echo '\''portsentry: attack from host: $TARGET$ on port: $PORT$'\'' >> /var/log/portsentry.log\""; next }
/^[[:space:]]*TCP_MODE[[:space:]]*=/ { print "TCP_MODE=\"atcp\""; next }
/^[[:space:]]*UDP_MODE[[:space:]]*=/ { print "UDP_MODE=\"audp\""; next }
{ print }
' $portsentry_conf.bak | sudo tee $portsentry_conf > /dev/null
sudo systemctl enable --now portsentry
echo '[Definition]
failregex = portsentry: attack from host: <HOST> on port: \d+
ignoreregex =' | sudo tee /etc/fail2ban/filter.d/portsentry.conf > /dev/null
echo '
[portsentry]
enabled = true
filter = portsentry
logpath = /var/log/portsentry.log
bantime = 86400
maxretry = 2
' | sudo tee -a /etc/fail2ban/jail.local > /dev/null
sudo systemctl restart fail2ban
Послесловие
На этом всё. Теперь ваш сервер — это уже не беззащитная пластиковая коробочка с root-доступом на 22-м порту, а вполне себе крепость, пусть и не с бастионами, но как минимум с дверьми, замками и вышибалой у входа.
Все предложенные шаги — это не серебряная пуля, не гарантия «неуязвимости», а просто проверенные временем и практикой базовые меры. То, что действительно работает и снижает риски. Ещё раз, я не претендую на полноту или абсолютную истину — просто поделился с миром теми шагами, которые сам применяю каждый раз, когда получаю новый VPS при развёртывании очередного бизнес‑проекта.
Если вы дочитали до этого места и применили хотя бы часть — уже хорошо. Если применили всё — теперь у вас сервер, к которому не так‑то просто будет постучаться.
До новых встреч и не забывайте про регулярные обновления системы!