Если сервер доступен из интернета, SSH становится первой точкой атаки. Автоматические сканеры начинают перебирать пароли через считанные минуты после того, как IP-адрес появляется в сети.
Ниже — настройка SSH для боевого сервера на Ubuntu и Debian: вход по ключу, запрет паролей, ограничение списка учётных записей, Fail2ban, межсетевой экран и автоматические обновления безопасности. Отдельное внимание уделено двум вещам, на которых чаще всего спотыкаются: как sshd собирает конфигурацию из нескольких файлов и чем отличается работа службы при активации сокетом.
Вся настройка ведётся под root. Это оправдано на машине с единственным администратором и убирает целый класс ошибок, когда ключ лежит у одной учётной записи, а вход разрешён другой. Плата — отсутствие разделения «повседневная работа / повышение привилегий» и невозможность установить по журналам, кто именно выполнил команду. Как только доступ к серверу получает второй человек, стоит перейти к персональным учётным записям с sudo.
Главный принцип материала: ни одна команда здесь не меняет способ запуска службы SSH. Ровно на этом теряют доступ чаще всего, а выигрыш — только косметический. Служба остаётся в том режиме, в котором её настроил провайдер образа, и всё руководство рассчитано на оба возможных режима.
- Почему нельзя оставлять настройки по умолчанию
- Шаг 0. Аварийный доступ и рабочие условия
- Шаг 1. В каком режиме работает ваша служба SSH
- Шаг 2. Генерация ключа ed25519
- Резервный ключ вместо второй учётной записи
- Шаг 3. Ключи в authorized_keys и права на файлы
- Шаг 4. Как sshd на самом деле читает конфигурацию
- Что уже лежит в каталоге
- Свой файл с наивысшим приоритетом
- Запрет cloud-init возвращать пароли
- Проверка синтаксиса
- Применение
- Что получилось на самом деле
- Контрольная проверка: пароль действительно не проходит
- Заодно: кто уже заходил на сервер
- Шаг 5. Межсетевой экран (UFW)
- Шаг 6. Fail2ban против перебора паролей
- Если служба не поднимается
- Шаг 7. Автоматические обновления безопасности
- Опционально: смена порта 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. Отсюда следуют два принципиальных момента, из-за которых привычная правка основного файла часто не даёт результата:
- Для каждого параметра sshd применяет первое встреченное значение. Не последнее, а именно первое. Всё, что встретится ниже по тексту для того же параметра, игнорируется без единого предупреждения.
- Директива 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-сервер опускаться не должен.









