Первоначальная настройка сервера Ubuntu 24.04
Пошаговая настройка свежеустановленного сервера: обновление, рабочий пользователь, файрвол, защита SSH, Caddy как реверс-прокси.
Техника безопасности. Шаги 5 и 8 могут отрезать вас от сервера при ошибке в конфиге. Всё время работы держите вторую открытую SSH-сессию и не закрывайте её, пока не убедитесь, что новая сессия открывается в третьем окне. Если
доступдоступа к консоли (IPMI, KVM, веб-консоль провайдера)отсутствуетнет —проверьте,обеспечьтечто он есть,его до начала работы.
Порядок шагов важен: сначала создаём пользователя и убеждаемся, что он заходит по ключу, и только потом закрываем root.
Каждый блок кода ниже рассчитан на копирование целиком: это либо одна команда, либо цепочка через &&, которая отработает и одной строкой.
1. Обновление системы
sudo apt update && sudo apt upgrade -y
Если обновилось ядроядро, или системные библиотеки, лучше сразу перезагрузиться,перезагружаемся, чтобы дальше работать на актуальной системе:
[ -f /var/run/reboot-required ] && sudo reboot
Опционально — автоматические обновления безопасности:безопасности. Установка:
sudo apt install -y unattended-upgrades
Интерактивная настройка (задаст один вопрос, ответить «Да»):
sudo dpkg-reconfigure -plow unattended-upgrades
2. Пользователь mrleo1nid с sudo без пароля
Создаём пользователяпользователя. Команда интерактивная: спросит пароль и добавляемнесколько необязательных полей, которые можно пропустить через Enter.
sudo adduser mrleo1nid
Добавляем в группу sudo:
sudo adduser mrleo1nid
sudo usermod -aG sudo mrleo1nid
adduser спросит пароль и несколько необязательных полей — их можно пропустить, нажимая Enter.
Sudo без пароля
ПравилаПравило пишем отдельным файлом в /etc/sudoers.d/, а не правкой основного /etc/sudoers — так изменение легко откатить и оно переживёт обновление пакета:
echo 'mrleo1nid ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/mrleo1nid && sudo chmod 440 /etc/sudoers.d/mrleo1nid
sudo visudo -c -f /etc/sudoers.d/mrleo1nid
ПоследняяПроверка командасинтаксиса обязательна:— синтаксическаяобязательна. ошибкаОшибка в sudoers ломает sudo целиком, и без root-доступа это уже не чинится.чинится:
sudo visudo -c -f /etc/sudoers.d/mrleo1nid
Ожидаемый вывод —вывод: parsed OK.
SSH-ключ
СЕсли ключа ещё нет, создаём его на локальной машинымашине:
ssh-keygen -t ed25519
Копируем на сервер, тоже с сервера):локальной машины:
ssh-copy-id mrleo1nid@СЕРВЕР
Если ключа ещё нет — ssh-keygen -t ed25519 на локальной машине.
Проверяем в отдельном окне, что вход работает и sudo не спрашивает пароль:работает:
ssh mrleo1nid@СЕРВЕР
И что sudo whoamiне #спрашивает пароль (должно вывести root):
sudo запроса пароляwhoami
Не переходите к шагу 8, пока это не заработало.
3. Переключение на iptables legacy
Ubuntu 24.04 по умолчанию использует backend nftables. Скрипт бана из шага 7 работает через классический iptables + ipset, поэтому переключаем систему в legacy-режим — тогда и UFW, и Docker,Docker и скрипт будут писать правила в одну и ту же таблицу, а не в две параллельные.
sudo apt install -y iptables ipset
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy && sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy && sudo update-alternatives --set arptables /usr/sbin/arptables-legacy && sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy
Проверка:Проверка — в выводе должно быть (legacy):
sudo iptables --version # должно быть (legacy)
Чтобы старые nft-правила не висели параллельно, проще всего перезагрузиться:перезагружаемся:
sudo reboot
Если на сервере установлен
Docker —Docker, после переключения его нужно перезапустить(sudo systemctl restart docker), он определяет— backend определяется пристарте.старте демона. Перезагрузка делает это сама.
4. Fail2ban
Банит IP после нескольких неудачных попыток входа.
sudo apt install -y fail2ban
СобственныеСвои настройки пишем в jail.local — jail.conf перезаписывается при обновлении пакета.пакета:
sudo nano /etc/fail2ban/jail.local:
Содержимое:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
# свои адреса, чтобы не забанить себя
ignoreip = 127.0.0.1/8 ::1 192.168.0.0/16 10.0.0.0/8
[sshd]
enabled = true
backend = systemd
backend = systemd заставляет читать журнал systemd вместо файла /var/log/auth.log — надёжнее, так как наличие этого файла зависит от того, установлен ли rsyslog.
Запускаем:
sudo systemctl enable --now fail2ban && sudo systemctl restart fail2ban
Проверяем, что jail поднялся:
sudo fail2ban-client status sshd
ПолезныеРазбанить команды:адрес, если забанили себя:
sudo fail2ban-client status sshd # список забаненных
sudo fail2ban-client set sshd unbanip 1.2.3.4 # разбанить
5. Файрвол
sudo apt install -y ufw
Сначала разрешаем SSH, потом включаем. Обратный порядок отрежет вас от сервера:сервера. Правила задаются одной цепочкой:
sudo ufw default deny incoming && sudo ufw default allow outgoing && sudo ufw allow OpenSSH && sudo ufw allow 80/tcp && sudo ufw allow 443/tcp
Только теперь включаем (команда спросит подтверждение — ответить y):
sudo ufw enable
Проверка:
sudo ufw status verbose
Порты 80 и 443 нужны Caddy: 80 — для ACME-проверки Let's Encrypt, без него сертификат не выпустится.
Docker и UFW. Если на сервере есть контейнеры с проброшенными портами (
ports:в compose), Docker пишет правила напрямую в iptables в обход UFW. Запрет вufwтакие порты не закроет. Ограничивать их нужно правилами в цепочкеDOCKER-USERлибо биндить публикацию на конкретный интерфейс:'127.0.0.1:8080:80'вместо'8080:80'.
6. Caddy
Реверс-прокси с автоматическим получением и продлением сертификатов Let's Encrypt.
Зависимости:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
Ключ репозитория:
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \ | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
Сам репозиторий:
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
Установка:
sudo apt update && sudo apt install -y caddy
КонфигПеред настройкой убеждаемся, что A-запись домена указывает на этот сервер:
dig +short example.com
Конфиг:
sudo nano /etc/caddy/Caddyfile:
example.com {
reverse_proxy 127.0.0.1:8080
}
Проверяем синтаксис и применяем:
sudo caddy validate --config /etc/caddy/Caddyfile && sudo systemctl reload caddy
sudo journalctl -u caddy -f
ВСмотрим логахлоги — должна появиться запись об успешно полученном сертификате. Перед этим убедитесь, что A-запись домена указывает на сервер:сертификате:
digsudo +shortjournalctl example.com-u caddy -f
Если Caddy стоит вторым в цепочке прокси
Когда перед ним есть ещё один Caddy/Caddy или nginx, во внутреннем блоке достаточно передать протокол:
example.com {
reverse_proxy http://10.10.1.129:8080 {
header_up X-Forwarded-Proto https
}
}
Дублировать header_up для Host, X-Real-IP и X-Forwarded-For не нужно: Caddy проставляет их сам, причём X-Forwarded-For дописывает к цепочке, а ручное header_up его затирает — приложение начнёт видеть IP прокси вместо реального клиента.
7. Блокировка IP по внешнему списку (ban.sh)
Скрипт из somebody-knocked-my-ssh скачивает список IP/CIDR, заливает его в ipset и вешает одно правило DROP в INPUT — вместо сотен отдельных правил на каждый адрес.
Скачиваем и делаем исполняемым:
sudo wget "https://raw.githubusercontent.com/jahlib/somebody-knocked-my-ssh/refs/heads/main/ban.sh" \
-O /usr/local/sbin/ban.sh && sudo chmod +x /usr/local/sbin/ban.sh
Перед первым запуском откройте файл и задайтезадаём переменную URL вверху скрипта — адрес текстового файла со списком (одна запись на строку, комментарии через #). Остальные параметры (SET_NAME, REMOVE_ORPHANS, LOCK_FILE) можно оставить по умолчанию.умолчанию:
sudo nano /usr/local/sbin/ban.sh
Первый запуск:
sudo bash /usr/local/sbin/ban.sh
Скрипт сам поставит ipset, если его нет, создаст набор, добавит правило и выведет строку-итог вида added / skipped / invalid / removed / total.
Автообновление по cron
sudo crontab -e
Добавить в конец:
@reboot sleep 60 && bash /usr/local/sbin/ban.sh
0 */6 * * * bash /usr/local/sbin/ban.sh
Строка @reboot важна: ipset живёт в памятипамяти, и после перезагрузки набор пуст, а правило iptables без него ничего не блокирует. Задержка даёт подняться сети.
Проверка
Сколько адресов в наборе:
sudo ipset list blacklist | grep "Number of entries"
Стоит ли правило в цепочке:
sudo iptables -L INPUT -n --line-numbers | grep blacklist
Замечания
- Только IPv4. Для IPv6 скрипт ничего не делает.
- Правило добавляется в конец
INPUT, а UFW ставит свои переходы в начало цепочки. Проверьте по--line-numbers, что порядок вас устраивает: если UFW разрешает порт раньше, чем сработает DROP, блокировка для этого порта не применится. - В режиме зеркала (
REMOVE_ORPHANS=1) пустой или битый удалённый список на очередной синхронизации разбанит всех. Следите за значениемremoved=в логе. - Список по URL стоит держать неугадываемым: скрипт не умеет авторизацию, и любой, кто найдёт адрес, увидит его содержимое.
Как удалить
sudo iptables -D INPUT -m set --match-set blacklist src -j DROP -m comment --comment "ipset-blacklist-drop" && sudo ipset destroy blacklist
8. Закрытие root-доступа
Выполнять только после того, как вход пользователем
mrleo1nidпо ключу иsudoбез пароля проверены в отдельной сессии.
Отключаем вход root по SSH
В Ubuntu 24.04 в /etc/ssh/sshd_config есть директива Include /etc/ssh/sshd_config.d/*.conf, поэтому правки удобнее держать отдельным файлом:
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf:
Содержимое:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
Проверяем синтаксис и применяем:применяем одной командой — при ошибке в конфиге sshd -t выведет её и не даст перезапустить сломанный сервис:
sudo sshd -t && sudo systemctl restart ssh
В новом окне проверяем, что вход по-прежнему работает:
sshdssh -tmrleo1nid@СЕРВЕР
И ошибкечто вroot конфигетеперь выведетполучает еёотказ:
ssh неroot@СЕРВЕР
даст перезапустить сломанный сервис.
Про смену порта SSH. В 24.04 sshd запускается через сокет-активацию (
ssh.socket), и директиваPortвsshd_configигнорируется. Порт меняется черезsudo systemctl edit ssh.socketс переопределениемListenStream=.
Проверяем в новом окне, что вход по-прежнему работает:
ssh mrleo1nid@СЕРВЕР
ssh root@СЕРВЕР # должно отказать
Убираем пароль root
sudo passwd -l root
-l блокирует пароль: локальный вход под root по паролю становится невозможен, sudo при этом работает как обычно.
Команда
passwd -d rootудаляет пароль, а не блокирует. При некоторых конфигурациях PAM пустой пароль означает вход вообще без пароля — на консоли иличерезприфизическийфизическомдоступдоступе это дыра. Используйте-l, если нет конкретной причины поступить иначе.
Проверка состояния (— во втором поле L—должно заблокирован)быть L:
sudo passwd -S root
Итоговая проверка
Backend iptables (ожидается legacy):
sudo iptables --version
#
Файрвол активен, открыты 22/80/443:
sudo ufw status verbose
#
Fail2ban портыработает:
sudo fail2ban-client status sshd
#
Чёрный работаетсписок загружен:
sudo ipset list blacklist | grep entries"Number #of списокentries"
загружен
Caddy запущен:
sudo systemctl status caddy
#
Root заблокирован:
sudo passwd -S root
#
Фактически применённая конфигурация SSH — полезно, когда настройки разбросаны по нескольким файлам и непонятно, какая победила:
sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication'
Последняя команда показывает фактически применённую конфигурацию SSH со всеми включёнными файлами — полезно, когда настройка есть в нескольких местах и непонятно, какая победила.
Что стоит сделать дополнительно
- Настроить бэкапы (
restic,borg) с выгрузкой на другой хост. - Поставить мониторинг доступности, чтобы узнавать о падении не от пользователей.
ПроверитьЧерез неделю работы просмотретьsudo journalctl -p err -bпосле первой недели работы— там обычно всплывает то, что настроено криво, но пока не мешает.