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

#   


Пошаговая настройка свежеустановленного сервера: обновление, рабочий пользователь, файрвол, защита SSH, Caddy как реверс-прокси.

> **Техника безопасности.** Шаги 5 и 8 могут отрезать вас от сервера при ошибке в конфиге. Всё время работы держите **вторую открытую SSH-сессию** и не закрывайте её, пока не убедитесь, что новая сессия открывается в третьем окне. Если доступа к консоли (IPMI, KVM, веб-консоль провайдера) нет — обеспечьте его до начала работы.

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

Каждый блок кода ниже рассчитан на копирование целиком: это либо одна команда, либо цепочка через `&&`, которая отработает и одной строкой.

---

## 1. Обновление системы

```bash
sudo apt update && sudo apt upgrade -y

```

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

```bash
[ -f /var/run/reboot-required ] && sudo reboot

```

Опционально — автоматические обновления безопасности. Установка:

```bash
sudo apt install -y unattended-upgrades

```

Интерактивная настройка (задаст один вопрос, ответить «Да»):

```bash
sudo dpkg-reconfigure -plow unattended-upgrades

```

---

## 2. Пользователь mrleo1nid с sudo без пароля

Создаём пользователя. Команда интерактивная: спросит пароль и несколько необязательных полей, которые можно пропустить через Enter.

```bash
sudo adduser mrleo1nid

```

Добавляем в группу `sudo`:

```bash
sudo usermod -aG sudo mrleo1nid

```

### Sudo без пароля

Правило пишем отдельным файлом в `/etc/sudoers.d/`, а не правкой `/etc/sudoers` — так изменение легко откатить и оно переживёт обновление пакета:

```bash
echo 'mrleo1nid ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/mrleo1nid && sudo chmod 440 /etc/sudoers.d/mrleo1nid

```

Проверка синтаксиса — **обязательна**. Ошибка в sudoers ломает sudo целиком, и без root-доступа это уже не чинится:

```bash
sudo visudo -c -f /etc/sudoers.d/mrleo1nid

```

Ожидаемый вывод: `parsed OK`.

### SSH-ключ

Если ключа ещё нет, создаём его **на локальной машине**:

```bash
ssh-keygen -t ed25519

```

Копируем на сервер, тоже с локальной машины:

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

```

Проверяем в отдельном окне, что вход работает:

```bash
ssh mrleo1nid@СЕРВЕР

```

И что sudo не спрашивает пароль (должно вывести `root`):

```bash
sudo whoami

```

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

---

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

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

```bash
sudo apt install -y iptables ipset

```

```bash
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)`:

```bash
sudo iptables --version

```

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

```bash
sudo reboot

```

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

---

## 4. Fail2ban

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

```bash
sudo apt install -y fail2ban

```

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

```bash
sudo nano /etc/fail2ban/jail.local

```

Содержимое:

```ini
[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.

Запускаем:

```bash
sudo systemctl enable --now fail2ban && sudo systemctl restart fail2ban

```

Проверяем, что jail поднялся:

```bash
sudo fail2ban-client status sshd

```

Разбанить адрес, если забанили себя:

```bash
sudo fail2ban-client set sshd unbanip 1.2.3.4

```

---

## 5. Файрвол

```bash
sudo apt install -y ufw

```

**Сначала разрешаем SSH, потом включаем.** Обратный порядок отрежет вас от сервера. Правила задаются одной цепочкой:

```bash
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`):

```bash
sudo ufw enable

```

Проверка:

```bash
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.

Зависимости:

```bash
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl

```

Ключ репозитория:

```bash
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

```

Сам репозиторий:

```bash
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list

```

Установка:

```bash
sudo apt update && sudo apt install -y caddy

```

Перед настройкой убеждаемся, что A-запись домена указывает на этот сервер:

```bash
dig +short example.com

```

Конфиг:

```bash
sudo nano /etc/caddy/Caddyfile

```

```
example.com {
    reverse_proxy 127.0.0.1:8080
}

```

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

```bash
sudo caddy validate --config /etc/caddy/Caddyfile && sudo systemctl reload caddy

```

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

```bash
sudo journalctl -u caddy -f

```

### Если 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](https://github.com/jahlib/somebody-knocked-my-ssh) скачивает список IP/CIDR, заливает его в `ipset` и вешает одно правило `DROP` в `INPUT` — вместо сотен отдельных правил на каждый адрес.

Скачиваем и делаем исполняемым:

```bash
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`) можно оставить по умолчанию:

```bash
sudo nano /usr/local/sbin/ban.sh

```

Первый запуск:

```bash
sudo bash /usr/local/sbin/ban.sh

```

Скрипт сам поставит `ipset`, если его нет, создаст набор, добавит правило и выведет строку-итог вида `added / skipped / invalid / removed / total`.

### Автообновление по cron

```bash
sudo crontab -e

```

Добавить в конец:

```
@reboot      sleep 60 && bash /usr/local/sbin/ban.sh
0 */6 * * *  bash /usr/local/sbin/ban.sh

```

Строка `@reboot` важна: `ipset` живёт в памяти, и после перезагрузки набор пуст, а правило `iptables` без него ничего не блокирует. Задержка даёт подняться сети.

### Проверка

Сколько адресов в наборе:

```bash
sudo ipset list blacklist | grep "Number of entries"

```

Стоит ли правило в цепочке:

```bash
sudo iptables -L INPUT -n --line-numbers | grep blacklist

```

### Замечания

- Только IPv4. Для IPv6 скрипт ничего не делает.
- Правило добавляется в конец `INPUT`, а UFW ставит свои переходы в начало цепочки. Проверьте по `--line-numbers`, что порядок вас устраивает: если UFW разрешает порт раньше, чем сработает DROP, блокировка для этого порта не применится.
- В режиме зеркала (`REMOVE_ORPHANS=1`) пустой или битый удалённый список на очередной синхронизации разбанит всех. Следите за значением `removed=` в логе.
- Список по URL стоит держать неугадываемым: скрипт не умеет авторизацию, и любой, кто найдёт адрес, увидит его содержимое.

### Как удалить

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

```bash
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf

```

Содержимое:

```
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes

```

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

```bash
sudo sshd -t && sudo systemctl restart ssh

```

В **новом окне** проверяем, что вход по-прежнему работает:

```bash
ssh mrleo1nid@СЕРВЕР

```

И что root теперь получает отказ:

```bash
ssh root@СЕРВЕР

```

> **Про смену порта SSH.** В 24.04 sshd запускается через сокет-активацию (`ssh.socket`), и директива `Port` в `sshd_config` игнорируется. Порт меняется через `sudo systemctl edit ssh.socket` с переопределением `ListenStream=`.

### Убираем пароль root

```bash
sudo passwd -l root

```

`-l` блокирует пароль: локальный вход под root по паролю становится невозможен, `sudo` при этом работает как обычно.

> Команда `passwd -d root` **удаляет** пароль, а не блокирует. При некоторых конфигурациях PAM пустой пароль означает вход вообще без пароля — на консоли или при физическом доступе это дыра. Используйте `-l`, если нет конкретной причины поступить иначе.

Проверка — во втором поле должно быть `L`:

```bash
sudo passwd -S root

```

---

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

Backend iptables (ожидается `legacy`):

```bash
sudo iptables --version

```

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

```bash
sudo ufw status verbose

```

Fail2ban работает:

```bash
sudo fail2ban-client status sshd

```

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

```bash
sudo ipset list blacklist | grep "Number of entries"

```

Caddy запущен:

```bash
sudo systemctl status caddy

```

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

```bash
sudo passwd -S root

```

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

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

```

---

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

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