nftables для продакшена: sets, maps, rate limiting, fail2ban и защита от DDoS на Linux-сервере

Полное руководство по настройке nftables для продакшен-серверов: sets и maps для фильтрации, динамические denylist-ы, rate limiting через meters, интеграция с fail2ban, DDoS-защита, geo-blocking и миграция с iptables — с рабочими примерами.

nftables 2026: rate limit, DDoS, fail2ban

Введение: почему nftables — стандарт файрвола Linux в 2026 году

Если вы до сих пор пишете правила iptables вручную — ну, как бы помягче это сказать — вы работаете с устаревшим инструментом. Начиная с 2022 года все основные дистрибутивы — Ubuntu 22.04+, Debian 11+, RHEL 9+, AlmaLinux 9+, Fedora 35+ — перешли на nftables как стандартную подсистему фильтрации пакетов. Та самая команда iptables, которую вы запускаете на современных системах, — это на самом деле iptables-nft, обёртка совместимости, транслирующая старый синтаксис в правила nftables.

Но переход на nftables — это не просто смена синтаксиса. Это качественно другой подход к управлению сетевой безопасностью.

Sets, maps, meters, конкатенированные совпадения, атомарные обновления правил — всё это делает nftables не просто заменой iptables, а полноценной платформой для построения продакшен-файрвола. Честно говоря, после того как я настроил пару серверов через нативный nftables, возвращаться к цепочкам iptables уже совершенно не хочется.

В этой статье мы не будем повторять базовые команды nft add rule. Вместо этого соберём полную конфигурацию для боевого сервера — с динамическими чёрными списками, rate limiting по source IP, интеграцией с fail2ban и защитой от DDoS-атак. Все примеры рабочие — можно скопировать и адаптировать под себя.

Архитектура nftables: что нужно знать перед настройкой

Прежде чем писать правила, стоит разобраться в фундаментальных отличиях архитектуры nftables от iptables. Это не косметические изменения — они определяют, как вы будете проектировать файрвол.

Иерархия: семейство → таблица → цепочка → правило

В iptables таблицы filter, nat, mangle были предопределены ядром. В nftables — ничего не существует по умолчанию. Вы сами создаёте таблицы, даёте им произвольные имена и назначаете семейство адресации.

Семейство inet — самый практичный выбор для серверов. Оно объединяет IPv4 и IPv6, позволяя описать правила один раз для обоих протоколов. Не нужно дублировать конфигурацию между iptables и ip6tables — а это, между прочим, одна из самых частых ошибок в продакшене, когда IPv4 защищён, а IPv6 остаётся полностью открытым.

# Основные семейства nftables:
# inet  — IPv4 + IPv6 (рекомендуется для серверов)
# ip    — только IPv4
# ip6   — только IPv6
# netdev — обработка на уровне устройства (ingress/egress)
# bridge — мостовые кадры

Цепочки: базовые и обычные

Базовые цепочки подключаются к хукам netfilter (input, forward, output, prerouting, postrouting). Обычные — вызываются из правил, примерно как пользовательские цепочки в iptables, только гибче.

Каждая базовая цепочка требует три параметра: тип (filter, nat, route), хук (точка перехвата в стеке netfilter) и приоритет (порядок обработки — чем меньше число, тем раньше выполняется).

Атомарные обновления

В iptables каждое изменение правила заставляло ядро пересоздавать весь набор. В nftables правила компилируются в байткод и выполняются виртуальной машиной ядра. Команда nft -f загружает конфигурацию атомарно — либо все изменения применяются успешно, либо остаётся предыдущая конфигурация. Никаких промежуточных состояний, когда половина правил на месте, а вторая ещё нет. Это, пожалуй, одно из самых важных улучшений с точки зрения надёжности.

Базовый каркас продакшен-конфигурации

Итак, начнём со скелета конфигурации /etc/nftables.conf, который будем постепенно наращивать. Политика по умолчанию — drop для входящего и транзитного трафика, accept для исходящего.

#!/usr/sbin/nft -f

# Очистка текущих правил для атомарной перезагрузки
flush ruleset

table inet firewall {

    # === SETS ===

    # Доверенные сети для управления
    set trusted_networks {
        type ipv4_addr
        flags interval
        elements = {
            10.0.0.0/8,
            172.16.0.0/12,
            192.168.0.0/16
        }
    }

    # Разрешённые TCP-порты
    set allowed_tcp_ports {
        type inet_service
        elements = { 22, 80, 443 }
    }

    # === ЦЕПОЧКА INPUT ===

    chain input {
        type filter hook input priority filter; policy drop;

        # Loopback — всегда разрешаем
        iif "lo" accept

        # Установленные и связанные соединения
        ct state established,related accept

        # Невалидные пакеты — сразу сбрасываем
        ct state invalid counter drop

        # ICMP — разрешаем с ограничением
        ip protocol icmp icmp type {
            echo-request,
            destination-unreachable,
            time-exceeded
        } limit rate 10/second accept

        # ICMPv6 — необходим для работы IPv6
        ip6 nexthdr icmpv6 icmpv6 type {
            echo-request,
            destination-unreachable,
            nd-neighbor-solicit,
            nd-neighbor-advert,
            nd-router-solicit,
            nd-router-advert
        } accept

        # Разрешённые TCP-порты
        tcp dport @allowed_tcp_ports accept

        # Логирование отброшенных пакетов (с ограничением)
        limit rate 5/minute burst 10 packets \
            log prefix "[nft-drop] " level warn

    }

    # === ЦЕПОЧКА FORWARD ===

    chain forward {
        type filter hook forward priority filter; policy drop;
        # По умолчанию запрещаем весь транзит
        # Раскомментируйте для Docker/контейнеров:
        # ct state established,related accept
        # iifname "docker0" accept
    }

    # === ЦЕПОЧКА OUTPUT ===

    chain output {
        type filter hook output priority filter; policy accept;
        # Исходящий трафик разрешён
    }
}

Этот каркас уже вполне рабочий: сервер принимает SSH, HTTP и HTTPS, отвечает на пинг с ограничением частоты, сбрасывает невалидные пакеты и логирует отброшенный трафик. Но это только основа — дальше будет интереснее.

Sets и Maps: эффективная фильтрация без дублирования правил

Sets — одна из ключевых возможностей, ради которых стоит переходить на nftables. Вместо написания отдельного правила для каждого IP-адреса или порта, вы группируете их в set и ссылаетесь одним правилом. Внутри sets используются хэш-таблицы, поэтому поиск занимает O(1) вне зависимости от количества элементов.

Set из 10 000 IP-адресов работает так же быстро, как set из 10. Это не преувеличение.

Именованные sets

Мы уже видели базовые sets в каркасе выше. Вот более продвинутые примеры.

# Set с интервалами — для блокировки подсетей
set blocked_ranges {
    type ipv4_addr
    flags interval
    elements = {
        45.155.205.0/24,    # Известные ботнеты
        185.220.101.0/24,   # Tor exit nodes (если нужно)
        193.142.146.0/24    # Сканеры
    }
}

# Set портов для мониторинга (только из доверенных сетей)
set monitoring_ports {
    type inet_service
    elements = { 9090, 9100, 3000 }  # Prometheus, Node Exporter, Grafana
}

# Правила использования sets
chain input {
    # ...
    ip saddr @blocked_ranges counter drop
    ip saddr @trusted_networks tcp dport @monitoring_ports accept
}

Maps для маршрутизации решений

Maps идут дальше sets — они сопоставляют ключ со значением. Это позволяет реализовать довольно сложную логику буквально одним правилом.

# Map: порт → действие (verdict map)
map port_policy {
    type inet_service : verdict
    elements = {
        22 : accept,
        80 : accept,
        443 : accept,
        8080 : drop,
        3306 : drop
    }
}

# Map: IP-адрес → VLAN-маркировка (data map)
map client_mark {
    type ipv4_addr : mark
    flags interval
    elements = {
        10.0.1.0/24 : 100,
        10.0.2.0/24 : 200,
        10.0.3.0/24 : 300
    }
}

# Применение verdict map — одно правило заменяет десять
chain input {
    tcp dport vmap @port_policy
}

Verdict maps особенно полезны, когда на одном сервере крутится множество сервисов. Вместо десятка правил «если порт X — accept, если порт Y — drop» получается одна таблица, которую легко читать и модифицировать.

Конкатенированные совпадения

А вот это — одна из моих любимых фич. nftables позволяет объединять несколько полей в одно совпадение, что значительно мощнее отдельных фильтров по IP и порту.

# Set с конкатенацией: IP + порт
set service_access {
    type ipv4_addr . inet_service
    elements = {
        10.0.1.5 . 3306,     # MySQL только с этого IP
        10.0.1.10 . 6379,    # Redis только с этого IP
        10.0.2.0/24 . 5432   # PostgreSQL только из этой подсети
    }
}

chain input {
    ip saddr . tcp dport @service_access accept
}

Одно правило — и каждый сервис доступен строго с разрешённых адресов. Попробуйте реализовать это в iptables без ipset — получите с десяток правил, и это ещё в лучшем случае.

Динамические sets и meters: автоматическая защита без внешних утилит

Динамические sets — пожалуй, самый мощный инструмент для автоматической реакции на аномалии. В отличие от статических sets, их элементы добавляются и удаляются самими правилами nftables в реальном времени. Никаких внешних скриптов — всё на уровне ядра.

Автоматический denylist с таймаутом

Давайте создадим set, который автоматически блокирует IP-адреса, устанавливающие слишком много соединений:

# Динамический set для временных банов
set denylist {
    type ipv4_addr
    flags dynamic, timeout
    timeout 15m          # Бан на 15 минут по умолчанию
}

# Set для отслеживания скорости подключений
set conn_rate {
    type ipv4_addr
    flags dynamic, timeout
    timeout 2m
}

chain input {
    # Проверка denylist — первым правилом!
    ip saddr @denylist counter drop

    # Автоматический бан при >20 новых соединений в минуту
    ct state new update @conn_rate {
        ip saddr limit rate over 20/minute
    } add @denylist { ip saddr } counter drop
}

Как это работает: каждое новое соединение проверяется через meter conn_rate. Если с одного IP поступает более 20 новых соединений в минуту, этот IP автоматически попадает в denylist и блокируется на 15 минут. По истечении таймаута — автоматическое удаление. Никаких скриптов, никаких cron-задач.

Двойной denylist для эскалации банов

Более продвинутый паттерн — эскалация наказания для повторных нарушителей. На практике это оказывается невероятно эффективным:

# Первый уровень — быстрый бан на 10 минут
set denylist_temp {
    type ipv4_addr
    flags dynamic, timeout
    timeout 10m
    size 65536
}

# Второй уровень — длительный бан на 4 часа для рецидивистов
set denylist_persist {
    type ipv4_addr
    flags dynamic, timeout
    timeout 4h
    size 65536
}

# Счётчики для мониторинга эффективности
counter cnt_temp_blocked { }
counter cnt_persist_blocked { }

chain input {
    # Сначала проверяем длительный бан
    ip saddr @denylist_persist counter name cnt_persist_blocked drop

    # Затем — временный бан
    ip saddr @denylist_temp counter name cnt_temp_blocked drop

    # Если IP попал во временный бан повторно — переводим в persist
    ct state new ip saddr @denylist_temp \
        add @denylist_persist { ip saddr } drop

    # Rate limiting с автоматическим добавлением во временный бан
    ct state new update @conn_rate {
        ip saddr limit rate over 15/minute
    } add @denylist_temp { ip saddr } drop
}

Итого: первое нарушение — бан на 10 минут. Попался снова — на 4 часа. Проверить, насколько хорошо это работает, можно простой командой:

# Просмотр счётчиков
nft list counters table inet firewall

# Пример вывода:
# counter cnt_temp_blocked { packets 12847 bytes 770820 }
# counter cnt_persist_blocked { packets 368198 bytes 21603012 }

# Просмотр содержимого denylist
nft list set inet firewall denylist_temp
nft list set inet firewall denylist_persist

Rate limiting SSH по source IP с meters

Meters — это специализированная форма динамических sets, заточенная под per-IP rate limiting:

# Ограничение SSH: максимум 3 новых подключения в минуту с одного IP
chain input {
    tcp dport 22 ct state new meter ssh_meter {
        ip saddr limit rate 3/minute burst 5 packets
    } accept

    # Всё, что превысило лимит — блокируем и логируем
    tcp dport 22 ct state new log prefix "[ssh-brute] " \
        add @denylist_temp { ip saddr } drop
}

# Ограничение HTTP/HTTPS: максимум 50 запросов в секунду с одного IP
chain input {
    tcp dport { 80, 443 } ct state new meter http_meter {
        ip saddr limit rate 50/second burst 100 packets
    } accept
}

Обратите внимание на параметр burst: он задаёт размер «ведра токенов» (token bucket) и определяет допустимый кратковременный всплеск. Для SSH разумно выставить 5 — этого хватит, чтобы открыть несколько сессий подряд, но перебор уже будет заблокирован. Для HTTP — 100, чтобы не мешать нормальной загрузке страниц с кучей ресурсов.

Интеграция nftables с fail2ban

Динамические sets отлично справляются с flood-атаками, но для более сложных паттернов — неудачных авторизаций SSH, ошибок аутентификации веб-приложений, подбора URL — нужен анализ логов. И тут на сцену выходит fail2ban.

Настройка fail2ban для работы с nftables

Fail2ban начиная с версии 0.11.2 нативно поддерживает nftables. Настройка довольно прямолинейная — редактируем /etc/fail2ban/jail.local:

# /etc/fail2ban/jail.local
[DEFAULT]
# Используем nftables вместо iptables
banaction = nftables-multiport
banaction_allports = nftables-allports
chain = input

# Глобальные параметры
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8

# Инкрементальные баны — наказание растёт с каждым нарушением
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 1w
bantime.overalljails = true

[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 5m
bantime = 2h

[sshd-aggressive]
enabled = true
port = 22
filter = sshd[mode=aggressive]
logpath = /var/log/auth.log
maxretry = 1
findtime = 1d
bantime = 1w

# Рецидивисты — бан на всех портах
[recidive]
enabled = true
banaction = nftables-allports
filter = recidive
logpath = /var/log/fail2ban.log
maxretry = 3
findtime = 1d
bantime = 4w

Создание выделенной таблицы для fail2ban

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

# /etc/fail2ban/action.d/nftables-common.local
[Init]
# Приоритет -10 — обработка до основной таблицы (priority 0)
nftables_family = inet
nftables_table  = f2b-table
nftables_chain  = f2b-chain
chain           = input
blocktype       = drop

Зависимость systemd: автоматический перезапуск

Есть нюанс, о котором часто забывают: если файрвол перезагружается, правила fail2ban исчезают. Решается привязкой fail2ban к nftables через systemd:

# /etc/systemd/system/fail2ban.service.d/override.conf
[Unit]
Requires=nftables.service
After=nftables.service
PartOf=nftables.service

# Перечитать конфигурацию systemd
# systemctl daemon-reload
# systemctl restart fail2ban

Теперь при рестарте nftables fail2ban автоматически перезапустится и пересоздаст свои правила. Мелочь, а без неё рано или поздно столкнётесь с неприятным сюрпризом.

Проверка работы связки

# Статус fail2ban
fail2ban-client status sshd

# Пример вывода:
# Status for the jail: sshd
# |- Filter
# |  |- Currently failed: 2
# |  |- Total failed:     847
# |  \`- File list:       /var/log/auth.log
# \`- Actions
#    |- Currently banned: 5
#    |- Total banned:     312
#    \`- Banned IP list:  203.0.113.50 198.51.100.23 ...

# Просмотр правил nftables, созданных fail2ban
nft list table inet f2b-table

# Ручное управление банами
fail2ban-client set sshd banip 203.0.113.100
fail2ban-client set sshd unbanip 203.0.113.100

Защита от DDoS: нативные возможности nftables

Сразу оговорюсь: nftables не заменит полноценное DDoS-митигирование на уровне провайдера или CDN. Но для защиты от низкоуровневых volumetric-атак и SYN-флудов нативные механизмы nftables работают на удивление хорошо — и при этом практически не нагружают CPU.

SYN flood protection

# Выделенная цепочка для DDoS-митигирования
chain ddos_protection {
    # SYN flood — ограничиваем новые SYN-пакеты
    tcp flags syn limit rate over 50/second burst 100 packets \
        counter drop

    # SYN-пакеты без ct state new — аномалия
    tcp flags syn ct state != new counter drop

    # Фрагментированные пакеты — часто используются в атаках
    ip frag-off & 0x1fff != 0 counter drop

    # XMAS-пакеты (все флаги TCP установлены)
    tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg \
        counter drop

    # NULL-пакеты (ни одного флага)
    tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop

    # Невалидные комбинации флагов
    tcp flags syn,fin counter drop
    tcp flags syn,rst counter drop
}

chain input {
    type filter hook input priority filter; policy drop;

    iif "lo" accept
    ct state established,related accept
    ct state invalid counter drop

    # Вызов цепочки DDoS-защиты
    jump ddos_protection

    # ... остальные правила
}

Conntrack tuning для высокой нагрузки

При серьёзном трафике таблица conntrack может переполниться — и тогда начнут отбрасываться вообще все новые соединения. Вот оптимизация параметров ядра, которая спасала меня не раз:

# /etc/sysctl.d/99-nftables-hardening.conf

# Максимальное число отслеживаемых соединений
net.netfilter.nf_conntrack_max = 1048576

# Уменьшение таймаутов для быстрого освобождения ресурсов
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 15
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 30

# SYN cookies — защита от SYN-флуда
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 65536

# Размер хэш-таблицы conntrack
net.netfilter.nf_conntrack_buckets = 262144

# Применить: sysctl -p /etc/sysctl.d/99-nftables-hardening.conf

Per-IP connection limiting

# Ограничение одновременных соединений с одного IP
chain input {
    # SSH — максимум 3 одновременных соединения
    tcp dport 22 meter ssh_connlimit {
        ip saddr ct count over 3
    } counter reject with tcp reset

    # HTTP/HTTPS — максимум 100 одновременных соединений
    tcp dport { 80, 443 } meter http_connlimit {
        ip saddr ct count over 100
    } counter drop
}

Geo-blocking: блокировка по странам через sets

nftables поддерживает sets с флагом interval, что позволяет загружать тысячи IP-диапазонов без потери производительности. На этом строится geo-blocking.

Подготовка geo-данных

#!/bin/bash
# geo-update.sh — скрипт загрузки geo-IP списков

GEOIP_DIR="/etc/nftables/geoip"
mkdir -p "$GEOIP_DIR"

# Загрузка актуальных данных (пример для ipdeny.com)
for country in cn kp ir; do
    curl -sS "https://www.ipdeny.com/ipblocks/data/aggregated/${country}-aggregated.zone" \
        -o "${GEOIP_DIR}/${country}.zone"
done

# Формирование set-файла для nftables
echo "define geo_blocked = {" > "${GEOIP_DIR}/blocked.nft"
for zone_file in "${GEOIP_DIR}"/*.zone; do
    while IFS= read -r cidr; do
        [ -n "$cidr" ] && echo "    ${cidr},"
    done < "$zone_file"
done >> "${GEOIP_DIR}/blocked.nft"
echo "}" >> "${GEOIP_DIR}/blocked.nft"

Использование в конфигурации nftables

# В основном файле /etc/nftables.conf
include "/etc/nftables/geoip/blocked.nft"

table inet firewall {
    set geo_blocked_ips {
        type ipv4_addr
        flags interval
        elements = $geo_blocked
    }

    chain input {
        # Блокировка geo-IP — одним правилом
        ip saddr @geo_blocked_ips counter drop

        # ...
    }
}

Автоматическое обновление через cron

# /etc/cron.weekly/geoip-update
#!/bin/bash
/usr/local/bin/geo-update.sh && \
nft -f /etc/nftables.conf && \
logger "nftables: geo-IP lists updated successfully"

Благодаря флагу interval и хэш-таблицам, set из 50 000 CIDR-диапазонов добавляет менее 1 мкс задержки на пакет. Это пренебрежимо мало — можно не беспокоиться о производительности.

Миграция с iptables: пошаговый план

Если у вас работающий продакшен с iptables, миграция требует аккуратности. Вот проверенный план, который позволяет перейти без простоя.

Шаг 1: экспорт и трансляция существующих правил

# Сохранение текущих правил iptables
iptables-save > /root/iptables-backup.rules
ip6tables-save > /root/ip6tables-backup.rules

# Трансляция в синтаксис nftables
iptables-restore-translate -f /root/iptables-backup.rules \
    > /root/nftables-translated.nft

# Просмотр результата
cat /root/nftables-translated.nft

Шаг 2: валидация трансляции

# Проверка синтаксиса без применения
nft -c -f /root/nftables-translated.nft

# Если есть ошибки — правим вручную
# Трансляция не идеальна: сложные правила могут
# потребовать ручной доработки

Шаг 3: параллельный запуск

# Применяем nftables-правила параллельно с iptables
# (они работают на разных уровнях)
nft -f /root/nftables-translated.nft

# Проверяем, что всё работает
nft list ruleset
ss -tlnp    # проверяем доступность сервисов
curl -I http://localhost  # тест HTTP

Шаг 4: переключение

# Убираем iptables
iptables -F
iptables -X
ip6tables -F
ip6tables -X

# Отключаем сервис iptables
systemctl disable iptables 2>/dev/null
systemctl disable ip6tables 2>/dev/null

# Включаем nftables
cp /root/nftables-translated.nft /etc/nftables.conf
systemctl enable nftables
systemctl start nftables

# Финальная проверка
nft list ruleset
systemctl status nftables

Важно: не удаляйте бэкап iptables-backup.rules до тех пор, пока не убедитесь, что nftables стабильно работает в продакшене хотя бы неделю. Серьёзно, не удаляйте.

Отладка и мониторинг nftables

Написать правила — полдела. Нужно ещё убедиться, что они работают как задумано, и уметь оперативно находить проблемы.

Трассировка пакетов

# Включение трассировки для конкретного IP
nft add rule inet firewall input ip saddr 203.0.113.50 \
    meta nftrace set 1

# Просмотр трассировки в реальном времени
nft monitor trace

# Пример вывода:
# trace id abc123 inet firewall input packet: iif "eth0"
#   ether saddr aa:bb:cc:dd:ee:ff ip saddr 203.0.113.50
#   ip daddr 10.0.1.5 tcp dport 22
# trace id abc123 inet firewall input rule tcp dport @allowed_tcp_ports accept
# trace id abc123 inet firewall input verdict accept

# Не забудьте отключить трассировку после отладки!
nft delete rule inet firewall input handle 

Счётчики и статистика

# Просмотр правил со счётчиками
nft list chain inet firewall input

# Именованные счётчики
nft list counters table inet firewall

# Сброс счётчиков
nft reset counters table inet firewall

# Список активных элементов в динамических sets
nft list set inet firewall denylist_temp

# Количество элементов
nft list set inet firewall denylist_temp | grep -c "timeout"

Интеграция с системным журналом

# Правило логирования с rate limiting
chain input {
    # Логирование перед drop
    limit rate 15/minute burst 30 packets \
        log prefix "[nft-input-drop] " level warn \
        counter
}

# Фильтрация логов
# journalctl -k --grep="nft-drop"

# Настройка rsyslog для выделения nftables-логов
# /etc/rsyslog.d/10-nftables.conf:
# :msg, contains, "[nft-" /var/log/nftables.log
# & stop

Полная продакшен-конфигурация

Ну что ж, давайте соберём всё вместе. Вот финальный файл конфигурации, объединяющий все рассмотренные техники:

#!/usr/sbin/nft -f
# /etc/nftables.conf — Production Firewall Configuration
# Дата: 2026 | Семейство: inet (IPv4+IPv6)

flush ruleset

table inet firewall {

    # ===== STATIC SETS =====

    set trusted_mgmt {
        type ipv4_addr
        flags interval
        comment "Сети управления — полный доступ к SSH и мониторингу"
        elements = { 10.0.0.0/8, 172.16.0.0/12 }
    }

    set allowed_tcp {
        type inet_service
        comment "Публичные TCP-порты"
        elements = { 80, 443 }
    }

    set blocked_ranges {
        type ipv4_addr
        flags interval
        comment "Статический чёрный список"
    }

    # ===== DYNAMIC SETS =====

    set denylist_short {
        type ipv4_addr
        flags dynamic, timeout
        timeout 15m
        size 65536
        comment "Автоматический бан — 15 минут"
    }

    set denylist_long {
        type ipv4_addr
        flags dynamic, timeout
        timeout 4h
        size 65536
        comment "Рецидивисты — бан на 4 часа"
    }

    # ===== COUNTERS =====

    counter cnt_blocked_static { comment "Статический чёрный список" }
    counter cnt_blocked_short { comment "Временные баны" }
    counter cnt_blocked_long { comment "Длительные баны" }
    counter cnt_syn_flood { comment "SYN flood drops" }
    counter cnt_invalid { comment "Invalid state drops" }
    counter cnt_total_dropped { comment "Всего отброшено" }

    # ===== DDoS MITIGATION CHAIN =====

    chain ddos_filter {
        tcp flags syn limit rate over 50/second burst 100 packets \
            counter name cnt_syn_flood drop
        tcp flags syn ct state != new drop
        ip frag-off & 0x1fff != 0 drop
        tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg drop
        tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 drop
        tcp flags syn,fin drop
        tcp flags syn,rst drop
    }

    # ===== INPUT CHAIN =====

    chain input {
        type filter hook input priority filter; policy drop;

        # --- Loopback ---
        iif "lo" accept

        # --- Connection tracking ---
        ct state established,related accept
        ct state invalid counter name cnt_invalid drop

        # --- Denylist checks (раньше всего) ---
        ip saddr @blocked_ranges counter name cnt_blocked_static drop
        ip saddr @denylist_long counter name cnt_blocked_long drop
        ip saddr @denylist_short counter name cnt_blocked_short drop

        # --- DDoS mitigation ---
        jump ddos_filter

        # --- ICMP ---
        ip protocol icmp icmp type {
            echo-request,
            destination-unreachable,
            time-exceeded
        } limit rate 10/second accept

        ip6 nexthdr icmpv6 icmpv6 type {
            echo-request,
            destination-unreachable,
            nd-neighbor-solicit,
            nd-neighbor-advert,
            nd-router-solicit,
            nd-router-advert
        } accept

        # --- SSH: только из доверенных сетей + rate limiting ---
        ip saddr @trusted_mgmt tcp dport 22 ct state new \
            meter ssh_rate { ip saddr limit rate 5/minute burst 10 packets } \
            accept

        # SSH brute-force — автоматический бан
        tcp dport 22 ct state new \
            add @denylist_short { ip saddr } \
            log prefix "[ssh-blocked] " counter drop

        # --- Публичные сервисы ---
        tcp dport @allowed_tcp ct state new \
            meter http_rate { ip saddr limit rate 60/second burst 150 packets } \
            accept

        # HTTP flood — автоматический бан
        tcp dport { 80, 443 } ct state new \
            add @denylist_short { ip saddr } counter drop

        # --- Мониторинг (только из доверенных) ---
        ip saddr @trusted_mgmt tcp dport { 9090, 9100, 3000 } accept

        # --- Логирование отброшенных ---
        limit rate 10/minute burst 20 packets \
            log prefix "[nft-drop] " level warn \
            counter name cnt_total_dropped
    }

    # ===== FORWARD CHAIN =====

    chain forward {
        type filter hook forward priority filter; policy drop;
    }

    # ===== OUTPUT CHAIN =====

    chain output {
        type filter hook output priority filter; policy accept;
    }
}

Автоматизация: проверка конфигурации в CI/CD

Файрвол — критическая инфраструктура. Одна ошибка в правилах может заблокировать доступ к серверу (и хорошо, если у вас есть IPMI или консоль). Автоматизированная проверка — не роскошь, а необходимость.

Скрипт валидации

#!/bin/bash
# validate-nftables.sh — проверка конфигурации перед деплоем

CONFIG_FILE="${1:-/etc/nftables.conf}"

echo "[*] Проверка синтаксиса..."
if ! nft -c -f "$CONFIG_FILE" 2>&1; then
    echo "[FAIL] Синтаксическая ошибка в $CONFIG_FILE"
    exit 1
fi
echo "[OK] Синтаксис корректен"

echo "[*] Проверка наличия базовых правил..."
REQUIRED_PATTERNS=(
    "policy drop"
    "ct state established"
    "ct state invalid"
    "iif.*lo.*accept"
)

for pattern in "${REQUIRED_PATTERNS[@]}"; do
    if ! grep -qE "$pattern" "$CONFIG_FILE"; then
        echo "[FAIL] Отсутствует обязательное правило: $pattern"
        exit 1
    fi
done
echo "[OK] Все обязательные правила на месте"

echo "[*] Проверка: SSH-доступ не заблокирован полностью..."
if ! grep -qE "dport 22.*accept|dport ssh.*accept" "$CONFIG_FILE"; then
    echo "[WARN] Не найдено правило, разрешающее SSH!"
    echo "       Убедитесь, что доступ обеспечивается другим способом."
fi

echo "[*] Все проверки пройдены"

Интеграция в GitLab CI / GitHub Actions

# .gitlab-ci.yml (фрагмент)
validate_firewall:
  stage: test
  image: debian:bookworm
  before_script:
    - apt-get update && apt-get install -y nftables
  script:
    - nft -c -f nftables.conf
    - bash validate-nftables.sh nftables.conf
  rules:
    - changes:
        - nftables.conf
        - nftables/**/*

FAQ: часто задаваемые вопросы

Чем nftables лучше iptables и стоит ли мигрировать?

Коротко — да, стоит. nftables — официальная замена iptables, принятая во всех основных дистрибутивах. Ключевые преимущества: единый инструмент nft для IPv4/IPv6 (вместо отдельных iptables/ip6tables), нативные sets и maps для O(1)-поиска, атомарные обновления, более читаемый синтаксис. Если ваши серверы на Ubuntu 22.04+, Debian 11+, RHEL 9+ — под капотом уже nftables через слой совместимости. Прямая настройка даёт полный контроль и доступ ко всем фишкам.

Как не заблокировать себя при настройке файрвола?

Три золотых правила: (1) всегда держите открытую SSH-сессию при изменении правил — она не будет прервана благодаря ct state established; (2) добавьте в cron временную задачу восстановления: */5 * * * * nft -f /etc/nftables.conf.backup — если потеряете доступ, через 5 минут вернётся рабочая конфигурация; (3) всегда проверяйте синтаксис через nft -c -f перед применением. Поверьте, лучше потратить 30 секунд на проверку, чем час на поездку в дата-центр.

Можно ли использовать nftables вместе с Docker?

Можно, но с оговорками. Docker манипулирует правилами nftables напрямую для проброса портов контейнеров. Его NAT-правила обрабатываются до ваших фильтров, что может привести к обходу файрвола. Рекомендации: (1) задайте DOCKER_IPTABLES=false в конфигурации Docker и управляйте NAT вручную; (2) используйте цепочку forward для контроля трафика контейнеров; (3) рассмотрите Docker-прокси или reverse proxy вместо прямого проброса портов.

Как защитить сервер от DDoS с помощью nftables?

nftables эффективно справляется с низкоуровневыми DDoS-атаками: SYN-флуд блокируется rate limiting'ом SYN-пакетов, невалидные TCP-флаги отсекаются выделенной цепочкой, per-IP connection limiting ограничивает одновременные соединения. Плюс обязательно настройте sysctl-параметры: tcp_syncookies, nf_conntrack_max. Но для серьёзных volumetric-атак (десятки Гбит/с) без защиты на уровне провайдера или CDN не обойтись — файрвол хоста физически не отфильтрует трафик, превышающий ширину канала.

Как мониторить работу nftables в реальном времени?

Несколько инструментов на выбор. Именованные счётчики (nft list counters) показывают статистику по категориям. Команда nft monitor trace позволяет трассировать путь конкретных пакетов через цепочки — незаменимо при отладке. Логирование через log prefix с rate limiting отправляет события в systemd journal и syslog. Для автоматизации можно экспортировать счётчики в Prometheus через nftables_exporter или парсить вывод nft -j list counters в JSON-формате.

Об авторе Tobias Lindqvist

Tobias spent six years on Canonical's kernel team in the LTS hardening group, mostly working on Livepatch tooling and the long tail of CVE backports nobody wants to do. He left in 2025 to consult independently with Nordic infrastructure customers running large Ubuntu fleets, where most of the work is AppArmor profiles, unattended-upgrades that don't break things, and explaining what kernel.unprivileged_userns_clone actually does. He co-maintains a small open-source tool for diffing kernel config across distributions and contributes occasionally to the linux-hardening mailing list. His side project is a Yocto-based minimal image for industrial gateways that boots in under four seconds with full secure boot. Tobias writes mostly about kernel-level security primitives - namespaces, seccomp, LSMs - with the assumption that you've already read the man page.