Skip to main content

Первоначальная настройка сервера 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.localjail.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
#
(legacy)

Файрвол активен, открыты 22/80/443:

sudo ufw status verbose
#
active,

Fail2ban портыработает:

22/80/443
sudo fail2ban-client status sshd
#
jail

Чёрный работаетсписок загружен:

sudo ipset list blacklist | grep entries"Number #of списокentries"
загружен

Caddy запущен:

sudo systemctl status caddy
#
running

Root заблокирован:

sudo passwd -S root
#
L

Фактически применённая конфигурация SSH — полезно, когда настройки разбросаны по нескольким файлам и непонятно, какая победила:

sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication'

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


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

  • Настроить бэкапы (restic, borg) с выгрузкой на другой хост.
  • Поставить мониторинг доступности, чтобы узнавать о падении не от пользователей.
  • ПроверитьЧерез неделю работы просмотреть sudo journalctl -p err -b после первой недели работы — там обычно всплывает то, что настроено криво, но пока не мешает.