Skip to main content

Первоначальная настройка сервера Ubuntu 24.04

Первоначальная настройка сервера 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 без пароля

Создаём пользователя и добавляем в группу 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-доступа это уже не чинится. Ожидаемый вывод — parsed OK.

SSH-ключ

С локальной машины (не с сервера):

ssh-copy-id mrleo1nid@СЕРВЕР

Если ключа ещё нет — ssh-keygen -t ed25519 на локальной машине.

Проверяем в отдельном окне, что вход работает и sudo не спрашивает пароль:

ssh mrleo1nid@СЕРВЕР
sudo whoami        # должно вывести root без запроса пароля

Не переходите к шагу 8, пока это не заработало.


3. Переключение на iptables legacy

Ubuntu 24.04 по умолчанию использует backend nftables. Скрипт бана из шага 7 работает через классический iptables + ipset, поэтому переключаем систему в legacy-режим — тогда и UFW, и 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

Проверка:

sudo iptables --version     # должно быть (legacy)

Чтобы старые nft-правила не висели параллельно, проще всего перезагрузиться:

sudo reboot

Если на сервере установлен Docker — после переключения его нужно перезапустить (sudo systemctl restart docker), он определяет backend при старте. Перезагрузка делает это сама.


4. Fail2ban

Банит IP после нескольких неудачных попыток входа.

sudo apt install -y fail2ban

Собственные настройки пишем в jail.localjail.conf перезаписывается при обновлении пакета.

/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
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
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

Конфиг /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-запись домена указывает на сервер:

dig +short example.com

Если 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, поэтому правки удобнее держать отдельным файлом:

/etc/ssh/sshd_config.d/99-hardening.conf:

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes

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

sudo sshd -t && sudo systemctl restart ssh

sshd -t при ошибке в конфиге выведет её и не даст перезапустить сломанный сервис.

Про смену порта 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 во втором поле — заблокирован):

sudo passwd -S root

Итоговая проверка

sudo iptables --version                   # (legacy)
sudo ufw status verbose                   # active, порты 22/80/443
sudo fail2ban-client status sshd          # jail работает
sudo ipset list blacklist | grep entries  # список загружен
sudo systemctl status caddy               # running
sudo passwd -S root                       # L
sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication'

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


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

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