Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors

Полная защита SSH на Ubuntu: ключи, Fail2ban и firewall

Linux и DevOps

Если сервер доступен из интернета, SSH становится первой точкой атаки. Автоматические сканеры начинают перебирать пароли через считанные минуты после того, как IP-адрес появляется в сети.

Ниже — настройка SSH для боевого сервера на Ubuntu и Debian: вход по ключу, запрет паролей, ограничение списка учётных записей, Fail2ban, межсетевой экран и автоматические обновления безопасности. Отдельное внимание уделено двум вещам, на которых чаще всего спотыкаются: как sshd собирает конфигурацию из нескольких файлов и чем отличается работа службы при активации сокетом.

Вся настройка ведётся под root. Это оправдано на машине с единственным администратором и убирает целый класс ошибок, когда ключ лежит у одной учётной записи, а вход разрешён другой. Плата — отсутствие разделения «повседневная работа / повышение привилегий» и невозможность установить по журналам, кто именно выполнил команду. Как только доступ к серверу получает второй человек, стоит перейти к персональным учётным записям с sudo.

Главный принцип материала: ни одна команда здесь не меняет способ запуска службы SSH. Ровно на этом теряют доступ чаще всего, а выигрыш — только косметический. Служба остаётся в том режиме, в котором её настроил провайдер образа, и всё руководство рассчитано на оба возможных режима.

Почему нельзя оставлять настройки по умолчанию

Стандартная конфигурация OpenSSH на многих образах виртуальных серверов допускает вход по паролю. Это создаёт постоянную фоновую нагрузку от переборщиков и оставляет реальный шанс на компрометацию: достаточно одного слабого пароля. Особенно на облачном образе, где имя учётной записи атакующему известно заранее — это root.

Без настройки сервер получает непрерывные попытки подбора (сотни и тысячи в сутки), разрастание журналов, рост задержек отклика при массовых атаках и риск полного захвата системы при удачном подборе.

Шаг 0. Аварийный доступ и рабочие условия

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

Первое. У панели управления вашего провайдера есть аварийная консоль (VNC, KVM, «веб-консоль»), и вы знаете, как её открыть и под каким паролем войти. Это единственный путь назад, если SSH перестанет отвечать.

Второе. На всё время настройки открыт второй терминал с рабочим подключением к серверу. Уже установленная сессия переживает изменения конфигурации, поэтому она остаётся страховкой до тех пор, пока новые настройки не проверены отдельным подключением.

Третье. Вы работаете в сессии root. Во всех командах ниже sudo не используется намеренно. Если вы вошли под обычной учётной записью — перейдите в сессию root командой sudo -i.

Правило на весь материал: текущую сессию не закрываем, пока вход по ключу не проверен из отдельного окна терминала.

Шаг 1. В каком режиме работает ваша служба SSH

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

ss -tlnp | grep -E ':22\b'

Смотрите на имя процесса в конце строки:

  • users:(("systemd",pid=1,...))активация сокетом. Порт слушает systemd, а отдельный процесс sshd поднимается на каждое входящее соединение. Так по умолчанию устроены Ubuntu 22.10 и новее, включая 24.04;
  • users:(("sshd",pid=...))классический демон. Один постоянно работающий процесс держит порт сам. Так работают Debian и Ubuntu до 22.10.

Запишите результат — он понадобится дважды. Дополнительно можно посмотреть состояние обеих единиц:

systemctl is-active ssh ssh.socket

При активации сокетом ответ обычно inactive и active; у классического демона — наоборот. Встречаются и гибридные образы, где обе единицы связаны зависимостями нестандартным образом. Именно поэтому мы их не касаемся: работающую конфигурацию провайдера незачем ломать ради удобства набора команд.

Шаг 2. Генерация ключа ed25519

Алгоритм ed25519 даёт лучшее сочетание стойкости, скорости и компактности. RSA остаётся допустимым только при длине от 4096 бит и только там, где ed25519 не поддерживается устаревшим оборудованием.

Ключ генерируется на своей машине, а не на сервере. Команда одинакова для Linux, macOS и Windows (PowerShell):

ssh-keygen -t ed25519 -C "рабочий-ноутбук" -f ~/.ssh/id_ed25519

Создаются два файла: id_ed25519 — закрытый ключ, остаётся только у вас; id_ed25519.pub — открытый ключ, его и передают на серверы.

На запрос пароля к ключу лучше ответить непустой парольной фразой: если файл ключа попадёт в чужие руки, она даст время на отзыв доступа. Чтобы не вводить фразу при каждом подключении, ключ добавляется в агент:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

Резервный ключ вместо второй учётной записи

При работе только под root вторая учётная запись не служит страховкой — её роль берёт на себя второй ключ. Заведите отдельный ключ на другом устройстве или просто резервный, положенный в надёжное место. Тогда потеря рабочего ноутбука не отрезает доступ и не требует аварийной консоли:

ssh-keygen -t ed25519 -C "резервный" -f ~/.ssh/id_ed25519_backup

Оба открытых ключа добавляются на сервер отдельными строками — об этом следующий шаг.

Закрытый ключ никогда не копируется на сервер, не пересылается в мессенджерах и не кладётся в репозиторий. На сервер уходит только файл с расширением .pub.

Шаг 3. Ключи в authorized_keys и права на файлы

На облачном образе ключ, указанный при создании машины, провайдер уже положил в /root/.ssh/authorized_keys. Поэтому шаг начинается не с создания файла, а с проверки его содержимого:

cat /root/.ssh/authorized_keys

Каждая строка — один ключ. Если своего ключа в файле нет или вы хотите добавить резервный, файл открывается в редакторе:

nano /root/.ssh/authorized_keys

Содержимое .pub-файла вставляется целиком, одной строкой, без переносов. Строка начинается с ssh-ed25519 и заканчивается комментарием, указанным при генерации. Сохранение — Ctrl+O, Enter, выход — Ctrl+X.

Пути здесь и далее указываются полностью, а не через ~. Это не педантизм: если команду выполнить в сессии другой учётной записи, ~ развернётся в другой каталог, и ключ уйдёт не туда. Обнаруживается такая ошибка обычно уже после потери доступа.

Владелец и права — sshd откажется использовать ключ, если файлы доступны на запись посторонним:

chown -R root:root /root/.ssh
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
chmod 700 /root

Последняя команда часто выпадает из инструкций, а без неё получается классическое «ключ правильный, а вход не проходит». При включённом StrictModes (значение по умолчанию — yes) sshd молча отказывается читать authorized_keys, если сам домашний каталог доступен на запись группе или всем.

Проверка результата:

ls -ld /root /root/.ssh /root/.ssh/authorized_keys

Ожидается владелец root во всех трёх строках и права drwx------, drwx------, -rw-------.

Подключение из отдельного окна терминала:

ssh root@<server_ip>

Если вход по ключу не проходит, причину покажет подробный вывод клиента:

ssh -vvv root@<server_ip>

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

Шаг 4. Как sshd на самом деле читает конфигурацию

Это ключевой раздел. В современных Ubuntu и Debian в начале файла /etc/ssh/sshd_config стоит директива подключения:

grep -n Include /etc/ssh/sshd_config

Типичный ответ — 12:Include /etc/ssh/sshd_config.d/*.conf. Отсюда следуют два принципиальных момента, из-за которых привычная правка основного файла часто не даёт результата:

  1. Для каждого параметра sshd применяет первое встреченное значение. Не последнее, а именно первое. Всё, что встретится ниже по тексту для того же параметра, игнорируется без единого предупреждения.
  2. Директива Include стоит в начале файла, поэтому содержимое каталога sshd_config.d/ читается раньше остальной части основной конфигурации. Внутри каталога файлы подключаются в алфавитном порядке: 00-* раньше 50-*, 50-* раньше 99-*.

Следствие, обратное интуиции: приоритет у файла с меньшим номером, а не с большим. И любой файл из sshd_config.d/ перекрывает основной sshd_config.

Что уже лежит в каталоге

grep -r '' /etc/ssh/sshd_config.d/

Пример реального вывода:

/etc/ssh/sshd_config.d/50-cloud-init.conf:PasswordAuthentication yes
/etc/ssh/sshd_config.d/60-cloudimg-settings.conf:PasswordAuthentication no

Здесь выигрывает 50-cloud-init.conf со значением yes: он читается первым, и запрет из файла с номером 60 не применяется. Правка основного sshd_config в такой ситуации не даёт вообще ничего — пароли остаются разрешёнными, хотя в файле написано no.

Обратная ситуация тоже встречается: на образах Ubuntu Cloud файл 60-cloudimg-settings.conf запрещает пароли ещё до всех ваших правок. Именно поэтому утилита ssh-copy-id на таких машинах не работает — ей нужен парольный вход, чтобы записать ключ. Ключ приходится добавлять вручную, как в шаге 3.

Свой файл с наивысшим приоритетом

Вместо правки основного файла и файлов cloud-init (последние перезаписываются при перезагрузке) создаётся отдельный файл. Он читается первым и гарантированно задаёт все критичные параметры:

nano /etc/ssh/sshd_config.d/00-hardening.conf

Содержимое файла:

# --- Аутентификация ---
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
PermitEmptyPasswords no
StrictModes yes

# --- Кому разрешён вход ---
AllowUsers root

# --- Ограничение попыток входа ---
MaxAuthTries 6
LoginGraceTime 30

# --- Ограничение каналов внутри соединения ---
MaxSessions 5

# --- Отключение неиспользуемых возможностей ---
X11Forwarding no
PermitTunnel no

# --- Разрыв зависших сессий ---
ClientAliveInterval 300
ClientAliveCountMax 2

Права на файл:

chmod 644 /etc/ssh/sshd_config.d/00-hardening.conf

PermitRootLogin и AllowUsers проверяются независимо, и вход должен быть разрешён обеими директивами. Оставленное по невнимательности PermitRootLogin no рядом с AllowUsers root закрывает доступ полностью — при формально безупречном синтаксисе. Проверка sshd -t этого не увидит.

Пояснения к параметрам:

  • PermitRootLogin prohibit-password разрешает root вход только по ключу. Значение выбрано вместо yes не для красоты: оно запрещает парольный вход для root даже если PasswordAuthentication где-то окажется включённым — например, после того как cloud-init вернёт свой файл. Это второй, независимый рубеж на том же направлении;
  • AllowUsers root ограничивает вход единственной учётной записью. Несколько имён перечисляются через пробел;
  • KbdInteractiveAuthentication — актуальное имя параметра; старое ChallengeResponseAuthentication объявлено устаревшим начиная с OpenSSH 8.7 и лишь принимается как синоним;
  • StrictModes yes — значение по умолчанию, указано явно, чтобы связь между правами на /root из шага 3 и работой ключа была видна прямо в конфигурации;
  • MaxAuthTries 6, а не 3. Каждый ключ, который клиент предлагает серверу, считается отдельной попыткой. Если в агенте лежит четыре-пять ключей, до нужного дело не доходит: Received disconnect: Too many authentication failures. Радикальнее эта проблема решается на стороне клиента — см. врезку ниже;
  • LoginGraceTime 30 — время на завершение аутентификации; сокращение со 120 секунд по умолчанию уменьшает число одновременно висящих сессий у переборщиков;
  • MaxSessions 5 вынесен в отдельный раздел намеренно: он ограничивает число каналов внутри уже установленного соединения, а не попытки входа. К защите от подбора отношения не имеет;
  • ClientAliveInterval и ClientAliveCountMax: сервер начинает опрос после 300 секунд тишины и разрывает связь после двух неотвеченных проб — то есть примерно через 15 минут в худшем случае, а не через 10, как иногда пишут.

Три настройки добавляют осознанно, а не «для полноты»: AllowTcpForwarding no ломает проброс портов (ssh -L, ssh -D) и туннели к базам данных; AllowAgentForwarding no ломает работу с приватными репозиториями через сервер и переходные подключения; AuthenticationMethods publickey жёстко фиксирует единственный способ входа, но конфликтует с двухфакторной аутентификацией.

Чтобы клиент не перебирал все ключи из агента, укажите нужный явно в ~/.ssh/config на своей машине:

Host myserver
    HostName <server_ip>
    User root
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

После этого подключение выполняется командой ssh myserver, и сервер получает ровно одну попытку аутентификации.

Запрет cloud-init возвращать пароли

Файл 50-cloud-init.conf перегенерируется при перезагрузке машины. Сам по себе он уже не опасен — наш файл читается раньше, — но лучше убрать источник расхождения:

nano /etc/cloud/cloud.cfg.d/99-disable-ssh-pwauth.cfg

Содержимое файла — одна строка:

ssh_pwauth: false

Только после этого удаляется файл, созданный cloud-init:

rm -f /etc/ssh/sshd_config.d/50-cloud-init.conf

Удаление без первого шага бесполезно: cloud-init создаст файл заново при следующей загрузке.

Проверка синтаксиса

Каталог /run/sshd создаётся при старте службы, а при активации сокетом существует только пока обрабатывается соединение. Без него проверка обрывается сообщением Missing privilege separation directory: /run/sshd, поэтому создаём его заранее — команда безвредна, если каталог уже есть:

mkdir -p /run/sshd
sshd -t; echo "код возврата: $?"

Нулевой код и отсутствие вывода означают, что синтаксис корректен. Ненулевой — дальше не идём, сначала правим файл. Это и есть главная страховка руководства: пока проверка не пройдена, конфигурация ни на что не влияет.

Применение

Здесь режим из шага 1 имеет значение.

При активации сокетом делать не нужно ничего. На каждое новое соединение поднимается отдельный процесс sshd, который читает конфигурацию заново, — правки действуют начиная со следующего подключения сами. Уже открытые сессии не затрагиваются. Команда systemctl reload ssh в этом режиме ответит Unit cannot be reloaded because it is inactive, и это не ошибка.

Классическому демону нужна перезагрузка конфигурации:

systemctl reload ssh

Именно reload, а не restart: перезагрузка конфигурации не разрывает уже установленные соединения.

Что получилось на самом деле

Итоговые значения после слияния всех файлов показывает только режим тестового вывода — не содержимое файлов:

mkdir -p /run/sshd && sshd -T | grep -Ei "permitrootlogin|passwordauthentication|kbdinteractive|pubkeyauthentication|allowusers|maxauthtries|strictmodes"

Ожидаемый результат:

permitrootlogin without-password
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no
strictmodes yes
maxauthtries 6
allowusers root

Деталь, которая сбивает с толку: вместо prohibit-password вывод показывает without-password. Это устаревший синоним того же значения, sshd печатает каноническую форму.

Отдельно убедитесь, что в конфигурации не осталось заготовок из инструкций. Это частая причина потери доступа: sshd -t синтаксической ошибки в имени не видит, а sshd -T печатает его как есть, и вывод выглядит правдоподобно. Каждое имя из AllowUsers должно существовать в системе и иметь непустой файл ключей:

id root
wc -l /root/.ssh/authorized_keys

Контрольная проверка: пароль действительно не проходит

Из отдельного окна терминала, не закрывая рабочую сессию, принудительно просим клиента войти по паролю:

ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no root@<server_ip>

Правильный ответ сервера — Permission denied (publickey). Он означает, что парольная аутентификация закрыта и остаётся единственный путь — ключ.

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

После ближайшей перезагрузки сервера имеет смысл повторить sshd -T: если 50-cloud-init.conf не вернулся, а значения те же, конфигурация устойчива.

Заодно: кто уже заходил на сервер

Пока пароли были разрешены, у переборщиков был шанс — и на облачном образе это окно приходится ровно на root. Стоит посмотреть, не воспользовался ли кто-то:

journalctl -t sshd --since "7 days ago" --no-pager | grep 'Accepted password'
last -a | head -20

Фильтр по метке -t sshd, а не по единице -u ssh: при активации сокетом записи попадают в журналы отдельных единиц вида ssh@0-...service, и фильтр по ssh.service пропустит почти всё.

Строки вида Accepted password for root from <адрес> — это успешные входы по паролю. Если адрес не ваш, настройкой SSH дело не ограничивается: систему нужно считать недоверенной и разбираться отдельно — искать посторонние процессы, задания планировщика и лишние ключи в authorized_keys.

Шаг 5. Межсетевой экран (UFW)

apt install ufw -y
ufw default deny incoming
ufw default allow outgoing
ufw limit 22/tcp
ufw --force enable

Ключ --force избавляет от вопроса Proceed with operation (y|n)?. Без него, если блок команд вставлен в терминал целиком, следующая строка будет съедена как ответ на этот вопрос.

ufw status verbose

Правило limit одновременно открывает порт и включает ограничение частоты подключений: адрес, открывший шесть и более соединений за 30 секунд, временно блокируется. Отдельное allow 22/tcp не нужно.

Включение UFW выполняется после добавления правила для SSH. Обратный порядок закрывает доступ к серверу немедленно.

Три уточнения. Ограничение частоты в UFW работает для IPv4; если у сервера есть публичный IPv6-адрес, нагрузку по нему возьмёт на себя Fail2ban. Если SSH переведён на нестандартный порт, в правиле указывается именно он. И помните про сам порог в шесть соединений за полминуты: во время настройки вы открываете второе окно, запускаете ssh -vvv, проверяете вход по паролю и по ключу — этого набегает быстро, а блокировка средствами UFW не оставляет записей в журнале Fail2ban и выглядит просто как «сервер не отвечает».

Шаг 6. Fail2ban против перебора паролей

apt update
apt install fail2ban python3-systemd -y

Свой внешний адрес понадобится для следующего файла:

curl -s https://ifconfig.me

Собственные настройки кладутся в отдельный файл, чтобы обновления пакета их не затирали:

nano /etc/fail2ban/jail.local

Содержимое файла:

[DEFAULT]
bantime  = 24h
findtime = 10m
maxretry = 3
backend  = systemd
banaction = ufw

# Растущий срок блокировки для повторных нарушителей
bantime.increment = true
bantime.factor    = 2
bantime.maxtime   = 30d

# Адреса, которые никогда не блокируются
ignoreip = 127.0.0.1/8 ::1 <ваш_адрес>

[sshd]
enabled = true
journalmatch = SYSLOG_IDENTIFIER=sshd

Про параметры:

  • backend = systemd обязателен для Debian 12 и свежих сборок Ubuntu: файла /var/log/auth.log там может не быть вовсе, журналы живут в journald. Без этой строки служба падает при старте с ошибкой об отсутствующем файле. Пакет python3-systemd нужен именно для этого режима;
  • journalmatch = SYSLOG_IDENTIFIER=sshd — критично при активации сокетом. Штатное правило отбирает записи по единице ssh.service, а в этом режиме каждое соединение обслуживает отдельная единица ssh@0-...service. Без этой строки Fail2ban запускается, показывает active (running) и не видит ни одной попытки входа — счётчик остаётся нулевым при любом количестве атак. Отбор по метке журнала работает в обоих режимах;
  • banaction = ufw заставляет Fail2ban добавлять запреты через UFW, а не мимо него. Иначе правила блокировок и правила межсетевого экрана живут в разных цепочках и мешают друг другу;
  • bantime.increment удваивает срок при каждом повторном нарушении вплоть до 30 суток;
  • в ignoreip обязательно добавьте собственный внешний адрес. При единственной учётной записи цена ошибочной самоблокировки особенно высока. С динамическим адресом смысла в этом нет, зато возрастает ценность аварийной консоли.

Синтаксис проверяется до запуска — команда собирает конфигурацию целиком и указывает файл и строку, если что-то не так:

fail2ban-client -d > /dev/null && echo "конфигурация корректна"
systemctl enable --now fail2ban

Ключ --now и включает автозапуск, и сразу поднимает службу, поэтому отдельная команда restart не нужна.

systemctl status fail2ban --no-pager
fail2ban-client status sshd

Первая команда должна показать active (running), вторая — счётчик обнаруженных попыток и перечень заблокированных адресов. Если через несколько минут работы публичного сервера счётчик остаётся нулевым — почти наверняка не сработал отбор записей из журнала, см. journalmatch выше.

Если сразу после запуска fail2ban-client отвечает Failed to access socket path: /var/run/fail2ban/fail2ban.sock. Is fail2ban running? — чаще всего это не ошибка конфигурации, а состязание за время: клиент обратился к сокету раньше, чем сервер успел его создать. Так бывает, когда команды запуска и проверки вставлены в терминал одним блоком. Повторите проверку через пару секунд.

Если служба не поднимается

journalctl -u fail2ban -n 50 --no-pager

Строка Server ready означает, что всё в порядке. Три типичные ошибки:

  • ModuleNotFoundError: No module named 'systemd' или Failed to initialize any backend for Jail 'sshd' — не установлен пакет python3-systemd;
  • Have not found any log file for sshd jail — обратная ситуация: backend = systemd не указан, а файла /var/log/auth.log в системе нет;
  • Unable to create socket — остался мёртвый каталог сокета после аварийного завершения; лечится командой mkdir -p /var/run/fail2ban и повторным запуском.

Ещё одно сообщение попадается почти всегда и ни на что не влияет: WARNING 'allowipv6' not defined in 'Definition'. Using default one: 'auto'. Fail2ban сам определяет доступность IPv6. Убрать его из журнала:

nano /etc/fail2ban/fail2ban.d/allowipv6.conf
[Definition]
allowipv6 = auto
systemctl restart fail2ban

Снять блокировку вручную:

fail2ban-client set sshd unbanip <ip_address>

Или сразу все, если непонятно, какой адрес заблокирован:

fail2ban-client unban --all

Стоит понимать границу применимости: после запрета паролей перебор физически не может привести к входу. Fail2ban решает другую задачу — снижает паразитную нагрузку и убирает шум из журналов. Его неработоспособность неприятна, но доступ к серверу и защищённость от подбора пароля от неё не зависят.

Шаг 7. Автоматические обновления безопасности

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

Проверка того, что механизм работает:

unattended-upgrades --dry-run --debug

Тонкая настройка живёт в отдельном файле:

nano /etc/apt/apt.conf.d/50unattended-upgrades

Два параметра там заслуживают внимания:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

Автоматическая перезагрузка нужна, потому что обновления ядра и системных библиотек без неё не вступают в силу. Время выбирается по наименьшей нагрузке. Для сервера с непрерывными службами перезагрузку оставляют выключенной, но тогда за появлением файла /var/run/reboot-required нужно следить самостоятельно.

Опционально: смена порта SSH

Перенос SSH с 22-го порта не добавляет стойкости — целенаправленное сканирование найдёт службу за минуты. Но он на порядок сокращает объём фонового шума в журналах.

Способ зависит от режима из шага 1, и это единственное место, где разница принципиальна.

При активации сокетом

Директива Port в конфигурации sshd в этом режиме игнорируется — порт задан в определении сокета. Менять его нужно там:

systemctl edit ssh.socket

В открывшемся редакторе добавляется:

[Socket]
ListenStream=
ListenStream=2222

Пустое присваивание в первой строке обязательно: оно сбрасывает значение по умолчанию. Без него сокет будет слушать оба порта.

systemctl daemon-reload
systemctl restart ssh.socket

Порт дописывается в файл с наивысшим приоритетом:

nano /etc/ssh/sshd_config.d/00-hardening.conf

В конец файла добавляется одна строка:

Port 2222

Если строка Port в файле уже есть — её нужно изменить, а не добавить вторую: при двух директивах служба будет слушать оба порта.

mkdir -p /run/sshd && sshd -t && systemctl restart ssh

Здесь нужен именно restart: смена порта требует пересоздания слушающего сокета, перезагрузка конфигурации его не пересоздаёт. Открытая сессия не разрывается.

Дальше одинаково для обоих режимов

Порт открывается в межсетевом экране:

ufw limit 2222/tcp

И указывается в правиле Fail2ban:

nano /etc/fail2ban/jail.local

Раздел [sshd] принимает вид:

[sshd]
enabled = true
journalmatch = SYSLOG_IDENTIFIER=sshd
port    = 2222
systemctl restart fail2ban

Проверка, что порт слушается:

ss -tlnp | grep -E ':2222\b'

Старое правило межсетевого экрана убирается только после того, как подключение по новому порту проверено из второго терминала:

ufw delete limit 22/tcp

Итоговая конфигурация

  • вход под root возможен только по ключу; парольный вход для root запрещён отдельной директивой, независимо от общего параметра;
  • парольная аутентификация отключена полностью, включая интерактивный запрос;
  • вход возможен только по ключу ed25519, в authorized_keys лежат рабочий и резервный ключи;
  • доступ ограничен списком конкретных учётных записей;
  • права на /root, /root/.ssh и authorized_keys согласованы с StrictModes;
  • все критичные параметры собраны в одном файле 00-hardening.conf с наивысшим приоритетом;
  • cloud-init не может вернуть пароли обратно после перезагрузки;
  • межсетевой экран включён, лишние порты закрыты, для SSH работает ограничение частоты подключений;
  • Fail2ban видит записи журнала в любом режиме запуска службы и блокирует перебор с нарастающим сроком, свой адрес внесён в исключения;
  • обновления безопасности устанавливаются автоматически;
  • способ запуска службы SSH остался тем, который настроил провайдер образа.

Такой набор закрывает базовые риски для любого публичного сервера. Следующие уровни — двухфакторная аутентификация, ключи на аппаратных токенах (ed25519-sk), вынос SSH за VPN и централизованный сбор журналов — надстраиваются поверх него. Отдельным пунктом стоит переход к персональным учётным записям с sudo: он становится обязательным в тот момент, когда доступ к серверу получает второй человек.

Частые ошибки

  • Переключение способа запуска службы SSH. Комбинация systemctl disable --now ssh.socket и systemctl enable --now ssh кочует по руководствам, но на части образов (в том числе у отдельных провайдеров) сокет прописан в зависимостях службы. После остановки сокета systemd считает службу неактивной, а реальный процесс sshd продолжает держать порт — и любая попытка запуска отвечает Address already in use. Выигрыш от переключения косметический, риск реальный. Не делайте этого;
  • несогласованные PermitRootLogin no и AllowUsers root — директивы проверяются независимо, доступ закрывается полностью при корректном синтаксисе;
  • заготовка вида <username>, оставшаяся в конфигурации дословно: sshd -t ошибки не видит, sshd -T печатает её как есть, и вывод выглядит правдоподобно;
  • правка только основного sshd_config без учёта каталога sshd_config.d/ — файл cloud-init молча перекрывает изменения;
  • уверенность, что «файл с бо́льшим номером побеждает» — sshd применяет первое встреченное значение, приоритет у меньших номеров;
  • непроверенные права на домашний каталог: при StrictModes yes доступ на запись группе или всем отключает вход по ключу без внятных сообщений;
  • ключ добавлен не в тот домашний каталог, потому что команды с ~ выполнялись в сессии другой учётной записи;
  • единственный ключ в authorized_keys без резервного — потеря устройства означает поход в аварийную консоль;
  • MaxAuthTries 3 при нескольких ключах в агенте — до нужного ключа дело не доходит, сервер отвечает Too many authentication failures;
  • ожидание, что ssh-copy-id сработает на облачном образе Ubuntu: пароли там запрещены ещё до всех правок;
  • Fail2ban без journalmatch при активации сокетом: служба работает, счётчик попыток вечно нулевой;
  • поиск попыток входа через journalctl -u ssh при активации сокетом — записи лежат в единицах ssh@N.service, нужен фильтр -t sshd;
  • проверка результата чтением файлов вместо sshd -T — только эта команда показывает реально применённые значения;
  • ожидание, что директива Port сработает при активации сокетом;
  • контрольная проверка парольного входа после установки Fail2ban — несколько намеренных отказов блокируют собственный адрес на сутки;
  • отсутствие своего адреса в ignoreip;
  • включение UFW до добавления правила для SSH, а также ufw enable без --force в блоке команд;
  • вставка запуска Fail2ban и проверки его состояния одним блоком команд — клиент обращается к сокету раньше, чем сервер его создал;
  • отсутствие аварийной консоли провайдера в закладках и незнание пароля от неё.

Заключение

Настройка занимает меньше двадцати минут, но радикально снижает риск компрометации сервера. Главное здесь — не список команд, а три понимания. Первое: sshd применяет первое встреченное значение параметра, подключаемые файлы читаются раньше основного, и собственный 00-hardening.conf снимает вопрос приоритетов раз и навсегда. Второе: способ запуска службы менять не нужно — достаточно знать, какой у вас, потому что от него зависят три команды, и ни одна из них не относится к защите. Третье: каждое утверждение о конфигурации проверяется командой, а не чтением файла, — sshd -T для параметров, ss -tlnp для слушающего порта, отдельное окно терминала для самого входа. Вход по ключу, закрытый парольный доступ, Fail2ban и межсетевой экран составляют тот минимум, ниже которого публичный Linux-сервер опускаться не должен.

Оцените статью
ctrllife.ru
Подписаться
Уведомить о
guest
0 комментариев
Старые
Новые Популярные
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x