Брандмауэр UFW на Ubuntu Server: от нуля до продакшена
Закрываем всё лишнее на VPS одной утилитой: политики по умолчанию, SSH без риска запереть себя снаружи, правила по IP и rate-limit, IPv6, логи — и две вещи, о которых молчат почти все гайды: Docker в обход ufw и NAT для VPN.
- Доступ через Termius и SSH-ключи
- Брандмауэр UFW на Ubuntu Server
- Fail2ban для SSH на VPSскоро
- Автообновления и unattended-upgradesскоро
- Что такое ufw и зачем он на VPS
- Перед стартом: страховка от самоблокировки
- Шаг 1 — установка и проверка состояния
- Шаг 2 — политики по умолчанию
- Шаг 3 — разрешаем SSH (до включения!)
- Шаг 4 — включаем файрвол
- Шаг 5 — веб-сервер и другие сервисы
- Правила по IP и подсетям
- Порядок, удаление и вставка правил
- IPv6 — не забыть вторую половину
- Логи: кто стучится на сервер
- Docker и ufw: главная ловушка
- Форвардинг и NAT для VPN
- Готовый базовый набор правил
- Частые проблемы
Что такое ufw и зачем он на VPS
UFW (Uncomplicated Firewall) — надстройка над сетевым фильтром ядра Linux. Писать правила iptables или nftables руками долго и легко ошибиться; ufw превращает их в человекочитаемые команды вида ufw allow 443/tcp. В Ubuntu 22.04 и 24.04 ufw под капотом работает через iptables-nft, то есть правила в итоге попадают в nftables.
Зачем он нужен, если на сервере «ничего лишнего не запущено»? Потому что лишнее появляется само: база данных, по умолчанию слушающая все интерфейсы, отладочная веб-панель, экспортер метрик, контейнер с открытым портом. Файрвол переворачивает логику: закрыто всё, кроме явно разрешённого.
Перед стартом: страховка от самоблокировки
Единственный по-настоящему опасный момент в работе с ufw — включить его, не разрешив SSH. Сессия оборвётся, и попасть на сервер можно будет только через VNC-консоль хостера. Две страховки:
- Знайте, где VNC/веб-консоль в панели хостера и что пароль пользователя (не только ключ) у вас есть — см. статью про SSH-ключи.
- Поставьте «мёртвую руку» — автоматическое отключение файрвола через 5 минут. Если всё прошло хорошо, отмените её; если связь пропала, ufw выключится сам.
# ставим таймер на отключение ufw через 5 минут sudo systemd-run --on-active=5min --unit=ufw-deadman /usr/sbin/ufw disable # ... настраиваем и включаем ufw, проверяем вход в новой сессии ... # всё работает — отменяем таймер sudo systemctl stop ufw-deadman.timer
Шаг 1 — установка и проверка состояния
В Ubuntu Server ufw обычно уже установлен, но выключен. Проверим:
sudo apt update && sudo apt install ufw -y sudo ufw status verbose # Status: inactive # что сейчас реально слушает порты — это список кандидатов на правила sudo ss -tulnp
Вывод ss -tulnp — полезная ревизия. Всё, что слушает 0.0.0.0 или [::], доступно из интернета. Всё, что слушает 127.0.0.1, — только локально, и для него правила не нужны.
Шаг 2 — политики по умолчанию
Политика по умолчанию срабатывает, если пакет не совпал ни с одним правилом. Классическая схема для сервера — запретить всё входящее и разрешить всё исходящее:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw default deny routed # трафик, который идёт "сквозь" сервер (Docker, VPN)
default deny outgoing — мощная защита (заражённый сервер не сможет «позвонить домой»), но тогда придётся явно разрешать DNS, NTP, apt-репозитории и всё, куда ходят ваши сервисы. Для первого сервера оставьте allow outgoing и вернитесь к этому, когда освоитесь.
Шаг 3 — разрешаем SSH (до включения!)
Есть три варианта — от простого к лучшему:
# вариант 1: по имени профиля приложения (порт 22/tcp) sudo ufw allow OpenSSH # вариант 2: если SSH на нестандартном порту sudo ufw allow 2222/tcp comment 'ssh' # вариант 3 (рекомендуется): с ограничением частоты подключений sudo ufw limit 22/tcp comment 'ssh rate-limit'
ufw limit разрешает подключение, но блокирует IP, который открывает 6 и более соединений за 30 секунд. Обычному человеку это не мешает, а примитивный брутфорс резко тормозит. Вместе с входом только по ключу и fail2ban это закрывает SSH практически полностью.
sudo ufw allow from 198.51.100.7 to any port 22 proto tcp. Это самый сильный вариант, но если провайдер сменит IP, вы останетесь снаружи — VNC-консоль хостера должна быть под рукой.
Шаг 4 — включаем файрвол
# последний раз убедимся, что SSH в списке sudo ufw show added sudo ufw enable # Command may disrupt existing ssh connections. Proceed with operation (y|n)? y # Firewall is active and enabled on system startup sudo ufw status verbose # Status: active # Logging: on (low) # Default: deny (incoming), allow (outgoing), deny (routed) # # To Action From # -- ------ ---- # 22/tcp LIMIT IN Anywhere # ssh rate-limit # 22/tcp (v6) LIMIT IN Anywhere (v6) # ssh rate-limit
Не закрывая текущую сессию, откройте новое подключение в Termius. Зашли — отменяйте «мёртвую руку» из раздела выше. ufw включён и будет подниматься автоматически при перезагрузке.
Шаг 5 — веб-сервер и другие сервисы
Открываем только то, что действительно должно быть доступно снаружи. Несколько типовых примеров:
# веб-сервер: HTTP нужен для выпуска сертификатов Let's Encrypt (HTTP-01) sudo ufw allow 80/tcp comment 'http' sudo ufw allow 443/tcp comment 'https' # HTTP/3 (QUIC) работает поверх UDP sudo ufw allow 443/udp comment 'http3' # несколько портов одним правилом — протокол обязателен sudo ufw allow 80,443/tcp # диапазон портов sudo ufw allow 60000:61000/udp comment 'mosh' # WireGuard sudo ufw allow 51820/udp comment 'wireguard'
iПрофили приложений
Пакеты вроде nginx и OpenSSH кладут свои профили в /etc/ufw/applications.d/. Удобно, когда сервис использует несколько портов:
sudo ufw app list # Available applications: # Nginx Full # Nginx HTTP # Nginx HTTPS # OpenSSH sudo ufw app info "Nginx Full" sudo ufw allow "Nginx Full"
Правила по IP и подсетям
Административные панели, базы данных и мониторинг не должны быть открыты всему интернету. Открывайте их только для конкретных адресов:
# всё с одного IP (например, ваш офис) sudo ufw allow from 198.51.100.7 # конкретный порт с одного IP sudo ufw allow from 198.51.100.7 to any port 5432 proto tcp comment 'postgres for office' # подсеть — например, приватная сеть между серверами хостера sudo ufw allow from 10.10.0.0/24 to any port 9100 proto tcp comment 'node_exporter' # только на определённом интерфейсе sudo ufw allow in on wg0 to any port 53 comment 'dns for vpn clients' # заблокировать надоедливую сеть sudo ufw deny from 203.0.113.0/24
deny молча отбрасывает пакет — сканер ждёт таймаута и не знает, есть ли там что-то. reject сразу отвечает «порт закрыт». Для входящего трафика из интернета используйте deny; reject удобен внутри своей сети, чтобы клиенты не висели на таймаутах.
Порядок, удаление и вставка правил
ufw проверяет правила сверху вниз, и первое совпадение решает. Поэтому deny from 203.0.113.0/24, добавленный после allow 443/tcp, для HTTPS не сработает — пакет уже пропущен правилом выше. Блокирующие правила ставьте в начало через insert:
sudo ufw status numbered # To Action From # [ 1] 22/tcp LIMIT IN Anywhere # [ 2] 80/tcp ALLOW IN Anywhere # [ 3] 443/tcp ALLOW IN Anywhere # [ 4] 22/tcp (v6) LIMIT IN Anywhere (v6) # ... # вставить правило на первую позицию sudo ufw insert 1 deny from 203.0.113.0/24 # удалить по номеру (номера сдвигаются после каждого удаления!) sudo ufw delete 3 # удалить по описанию — надёжнее в скриптах sudo ufw delete allow 80/tcp # посмотреть изменения без применения sudo ufw --dry-run allow 8443/tcp # полный сброс к заводским настройкам (выключит ufw!) sudo ufw reset
reset удаляет все правила и выключает файрвол, сохраняя бэкапы старых файлов в /etc/ufw/*.rules.<дата>. После сброса снова начинайте с шага 2 — и снова с SSH до enable.
IPv6 — не забыть вторую половину
Если у VPS есть IPv6-адрес, а файрвол фильтрует только IPv4, все ваши сервисы открыты через IPv6. В Ubuntu поддержка включена по умолчанию, но проверьте:
grep IPV6 /etc/default/ufw
# IPV6=yes ← должно быть так
При IPV6=yes каждое правило без указания адреса автоматически создаётся в двух вариантах — это те самые строки с пометкой (v6) в выводе status. Если поменяли значение, выполните sudo ufw reload.
Логи: кто стучится на сервер
# уровень: off | low | medium | high | full (low — разумный дефолт) sudo ufw logging low # живой поток заблокированных пакетов sudo tail -f /var/log/ufw.log # топ-10 IP, которые стучались в закрытые порты sudo grep "UFW BLOCK" /var/log/ufw.log | grep -oP 'SRC=\K[^ ]+' | sort | uniq -c | sort -rn | head # топ-10 портов, которые сканируют sudo grep "UFW BLOCK" /var/log/ufw.log | grep -oP 'DPT=\K[0-9]+' | sort | uniq -c | sort -rn | head
Запустите последние две команды через сутки после включения ufw — обычно наверху окажутся 22, 23, 3389, 445 и 5900. Это наглядно показывает, от чего файрвол вас уже защищает.
Docker и ufw: главная ловушка
Это самый важный раздел статьи для всех, кто запускает контейнеры. Docker публикует порты в обход правил ufw. Когда вы пишете -p 8080:80 или ports: - "8080:80" в compose, Docker добавляет собственные правила NAT и пропускает трафик через цепочку FORWARD, а ufw фильтрует входящий трафик в цепочке INPUT. В итоге ufw status показывает, что 8080 закрыт, а из интернета контейнер прекрасно открывается.
ufw status, это ещё не значит, что он закрыт. Для Docker проверяйте снаружи — nmap с другой машины или онлайн-сканер портов.01Решение для 90% случаев: публикуйте на localhost
Если сервис стоит за реверс-прокси (nginx, Caddy, Nginx Proxy Manager), публиковать его порт на внешний интерфейс вообще не нужно. Привяжите его к 127.0.0.1 — тогда снаружи до него не достучаться, а прокси на том же сервере достанет:
services:
app:
image: ghcr.io/example/app:latest
ports:
- "127.0.0.1:8080:80" # ← вместо "8080:80"
db:
image: postgres:16
# порты не публикуем вовсе — app ходит в db по внутренней сети compose
Для разового доступа к такой панели с вашего ПК используйте SSH-туннель — в Termius это Port Forwarding.
02Если порт должен быть открыт, но с фильтрацией
Когда контейнер должен быть доступен снаружи, но только с определённых IP, нужно, чтобы ufw управлял и маршрутизируемым трафиком. Популярное готовое решение — скрипт ufw-docker (проект chaifeng/ufw-docker на GitHub): он добавляет в /etc/ufw/after.rules блок, который заставляет трафик к контейнерам проходить через правила ufw, после чего доступ открывается командами вида:
# открыть порт 80 контейнера app для всех sudo ufw-docker allow app 80 # или вручную — route-правило на порт ВНУТРИ контейнера sudo ufw route allow proto tcp from 198.51.100.7 to any port 80
"iptables": false в /etc/docker/daemon.json. Не делайте этого: Docker перестанет настраивать NAT, и у контейнеров пропадёт доступ в интернет, а сетевые проблемы будут проявляться неочевидно. Привязка к 127.0.0.1 или ufw-docker — правильные пути.
Форвардинг и NAT для VPN
Если на VPS работает VPN (WireGuard, AmneziaWG, OpenVPN), сервер должен пересылать трафик клиентов в интернет. При default deny routed ufw это запрещает — нужно разрешить форвардинг и настроить маскарадинг. Пример для WireGuard с подсетью клиентов 10.8.0.0/24 и внешним интерфейсом eth0 (проверьте свой: ip route | grep default):
01Включаем пересылку пакетов
net/ipv4/ip_forward=1 net/ipv6/conf/default/forwarding=1 net/ipv6/conf/all/forwarding=1
02Добавляем NAT в before.rules
Блок *nat добавляется в самое начало файла /etc/ufw/before.rules, перед строкой *filter:
# NAT для VPN-клиентов
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
COMMIT
03Разрешаем маршрут и порт VPN
sudo ufw allow 51820/udp comment 'wireguard' sudo ufw route allow in on wg0 out on eth0 comment 'vpn -> internet' sudo ufw reload # проверка sudo ufw status verbose | grep -E "routed|wg0" sysctl net.ipv4.ip_forward # = 1
sudo ufw status и sudo ss -tulnp: панель управления не должна торчать в интернет на случайном порту — закройте её и ходите через SSH-туннель.
Готовый базовый набор правил
Скрипт для свежего VPS с веб-сервером. Запускать при открытой второй SSH-сессии:
#!/usr/bin/env bash set -euo pipefail SSH_PORT=22 # поменяйте, если перенесли SSH # страховка: выключить ufw через 5 минут, если что-то пошло не так systemd-run --on-active=5min --unit=ufw-deadman /usr/sbin/ufw disable || true ufw --force reset ufw default deny incoming ufw default allow outgoing ufw default deny routed ufw limit "${SSH_PORT}/tcp" comment 'ssh' ufw allow 80/tcp comment 'http' ufw allow 443/tcp comment 'https' ufw allow 443/udp comment 'http3' ufw logging low ufw --force enable ufw status verbose echo echo "Проверьте вход в НОВОЙ сессии, затем выполните:" echo " sudo systemctl stop ufw-deadman.timer"
✓Шпаргалка команд
| Команда | Что делает |
|---|---|
| ufw status verbose | состояние, политики и правила |
| ufw status numbered | правила с номерами для удаления |
| ufw allow 443/tcp | открыть порт |
| ufw limit 22/tcp | открыть с rate-limit (6 подключений / 30 с) |
| ufw allow from IP to any port N | открыть порт только для IP |
| ufw insert 1 deny from NET | заблокировать сеть с наивысшим приоритетом |
| ufw delete N | удалить правило по номеру |
| ufw route allow in on X out on Y | разрешить форвардинг между интерфейсами |
| ufw reload | перечитать конфигурацию после правки файлов |
| ufw disable / enable | выключить / включить файрвол |
Частые проблемы
| Симптом | Причина и решение |
|---|---|
| После ufw enable пропал SSH | не разрешили SSH или он на другом порту. Зайдите через VNC-консоль хостера: sudo ufw allow <порт>/tcp или sudo ufw disable |
| Порт закрыт в ufw, но открыт снаружи | его публикует Docker. Привяжите порт к 127.0.0.1 или используйте ufw-docker |
| Правило deny не работает | выше в списке стоит allow для того же трафика. Используйте ufw insert 1 … |
| VPN-клиенты подключаются, но нет интернета | не включён ip_forward, нет NAT-блока в before.rules или route-правила; проверьте имя внешнего интерфейса |
| ERROR: Could not find a profile matching 'Nginx Full' | nginx не установлен из пакета — профиля нет. Используйте порты: ufw allow 80,443/tcp |
| Сервис открыт по IPv6 | IPV6=no в /etc/default/ufw. Поменяйте на yes и выполните ufw reload |
| Себя же заблокировал ufw limit | скрипт или IDE открывает много SSH-соединений подряд. Добавьте свой IP отдельным allow выше правила limit |
Итог: входящий трафик закрыт по умолчанию, SSH открыт с rate-limit, наружу смотрят только 80 и 443, контейнеры не публикуют порты мимо файрвола, а VPN-трафик идёт через явно разрешённый маршрут. Следующий слой — fail2ban, который умеет банить через ufw: достаточно указать banaction = ufw в его конфигурации.