Если вы всё ещё поддерживаете длинные цепочки iptables или ipset ради блоклистов, самое время мигрировать на nftables. Наборы (sets) и карты (maps) в nftables дают быстрые поиски по IP/CIDR, удобные таймауты для временных банов, динамическое наполнение прямо из правил и атомарные обновления без «дребезга» и окон уязвимости. Ниже — практическая схема, проверенные паттерны и тонкости производительности для админов, которые хотят держать чёткий и быстрый firewall на проде.
Зачем sets в nftables и чем это лучше iptables/ipset
Главная боль классического подхода — большие списки адресов размножают правила и замедляют обработку. Nftables хранит элементы в наборах, а правило всего одно: обращаться к набору по ключу. Для десятков и сотен тысяч IP/CIDR это критично — lookup идёт в оптимизированных структурах (hash/rbtree), без линейного прохода цепочки.
В отличие от ipset, сеты и карты — это «первоклассные граждане» в синтаксисе nftables: вы объявляете их в таблице, используете в правилах, обновляете атомарно и можете добавлять элементы «на лету» из самих правил (dynset). Плюс поддержка timeout на элемент, объединение CIDR при флаге interval, конкатенированные ключи (например, IP+порт) и карты vmap до вердикта без цепочек.
Базовый каркас: таблица, цепочка, два блоклиста
Обычно удобно собирать всё в семейственном inet, чтобы одинаково ловить IPv4 и IPv6. Минимальная «скелетная» конфигурация:
table inet filter {
sets {
blacklist4 {
type ipv4_addr
flags interval
timeout 1h
}
blacklist6 {
type ipv6_addr
flags interval
timeout 1h
}
}
chain input {
type filter hook input priority 0;
# Разрешения и allowlist — выше по цепочке
# ...
ip saddr @blacklist4 drop
ip6 saddr @blacklist6 drop
# Остальная политика
# ...
}
}
Здесь блоклисты поддерживают CIDR (флаг interval) и «горят» по таймауту, если элемент был добавлен как временный. Если вы импортируете статический список без сроков, можно не задавать timeout в определении набора, а указывать его только у конкретных элементов, где это нужно.
Для сценариев с несколькими исходящими IP при NAT посмотрите материал: управление SNAT с несколькими IP в nftables.

Тип набора и флаги: что выбрать для задач
type ipv4_addr/ipv6_addr— адресный блоклист по IP.flags interval— для списков с подсетями: ядро объединяет смежные интервалы и экономит память и операции.timeout— дефолтный срок жизни элементов. Можно переопределять на уровне добавляемого элемента.- Конкатенация ключей:
type ipv4_addr . inet_serviceилиtype ipv4_addr . ipv4_addr— удобно для правил «IP+порт», «источник+назначение» и т.п.
Операции с наборами: добавить, убрать, посмотреть
# Добавить сетевой префикс и одиночный IP
nft add element inet filter blacklist4 { 192.0.2.0/24, 203.0.113.5 }
# Временный бан на 2 часа с комментарием
nft add element inet filter blacklist4 { 198.51.100.10 timeout 2h comment "abuse" }
# Удалить элементы
nft delete element inet filter blacklist4 { 203.0.113.5, 192.0.2.0/24 }
# Просмотр содержимого
nft list set inet filter blacklist4
# Проверить наличие конкретного адреса
nft get element inet filter blacklist4 { 198.51.100.10 }
Все эти операции атомарны на уровне netlink: вы не «поймаете» момент, когда набор наполовину обновлён. Это особенно важно для больших импортов.
Динамические баны из правил (dynset)
Nftables позволяет добавлять элементы в набор прямо из правила при совпадении условия. Это удобно для примитивной самозащиты: слишком частые соединения, невалидные флаги TCP, подозрительные пробы портов. Пример временного бана для грубой силы на SSH по скорости новых соединений:
table inet filter {
sets {
ssh_abusers {
type ipv4_addr
timeout 1h
}
}
chain input {
type filter hook input priority 0;
# Сначала проверяем бан
ip saddr @ssh_abusers drop
# Если слишком часто стучатся в 22/tcp — добавить в бан на 1 час
tcp dport 22 ct state new limit rate over 20/minute add @ssh_abusers { ip saddr timeout 1h } drop
# ... остальной трафик
}
}
Не превращайте dynset в IDS: держите условия простыми, задавайте явный таймаут и обязательно проверяйте allowlist раньше автобанов.
vmap: карты до вердикта для «плоских» блоклистов
Если список большой и его задача — однозначно дать вердикт, используйте карту до вердикта. Правило будет короче, а ядро придёт к решению без дополнительных сравнений:
table inet filter {
sets {
blackmap4 {
type ipv4_addr : verdict
elements = { 203.0.113.5 : drop, 192.0.2.0/24 : drop }
}
}
chain input {
type filter hook input priority 0;
ip saddr vmap @blackmap4
}
}
Карта вернёт drop для совпадающих источников и «ничего» — для остальных, т.е. управление пойдёт дальше по цепочке. Удобно для плоских чёрных/белых списков портов, IP и префиксов.
Импорт больших блоклистов: атомарно и без простоев
Главные правила большого импорта: делать его атомарно и не разрушать рабочий набор до готовности нового. Базовый рецепт:
- Создайте новый временный набор с теми же параметрами (
blacklist4_new). - Залейте элементы в
blacklist4_newпачками. - Поменяйте местами наборы (
swap set), чтобы правила увидели новый контент мгновенно. - Удалите старый набор.
# Шаг 1: завести временный набор
nft add set inet filter blacklist4_new { type ipv4_addr; flags interval; }
# Шаг 2: загрузить элементы (примерно)
nft add element inet filter blacklist4_new { 192.0.2.0/24, 203.0.113.0/24, 203.0.113.5 }
# Шаг 3: атомарная ротация
nft swap set inet filter blacklist4 blacklist4_new
# Шаг 4: подчистить
nft delete set inet filter blacklist4_new
Скрипт импорта должен удалить дубликаты и валидировать синтаксис адресов до попадания в nft. Для CIDR включайте флаг interval — ядро само оптимизирует пересечения.

Структурирование: allowlist над блоклистом
На боевых серверах держите набор допустимых источников отдельно и проверяйте его раньше всех блокировок. Это спасёт вас от случайной самоблокировки при широких масках и даст понятный механизм исключений.
sets {
allowlist4 { type ipv4_addr; flags interval; }
}
chain input {
ip saddr @allowlist4 accept
ip saddr @blacklist4 drop
# ...
}
Производительность: hash против interval, IPv4/IPv6, размер
В общих чертах: одиночные адреса «без интервалов» оптимально ложатся в хеш; когда вы включаете flags interval и работаете с CIDR/диапазонами, ядро применяет дерево интервалов. На ядрах LTS (5.10+) сотни тысяч элементов обрабатываются предсказуемо и быстро, а разница в латентности lookup — единицы микросекунд по сравнению с «пустым» firewall, на типичных VDS/серверных конфигурациях это не заметно. Куда легче «подстрелить» производительность — сложной логикой в цепочках до проверки набора. Сначала наборы, потом всё остальное.
Конкатенированные ключи: IP + порт и не только
Когда задача — не просто забанить IP, а заблокировать конкретный сервис/порт от некоторых источников, используйте конкатенацию типов ключа:
set bad_http {
type ipv4_addr . inet_service
}
chain input {
tcp dport 80 ip saddr . tcp dport @bad_http drop
}
Так вы получите точечный контроль без дублирования логики и без длинных списков «если порт — то бан» в правилах.
Мониторинг и отладка
nft list ruleset— общий дамп правил и наборов.nft -a list ruleset— то же с хэндлами для точечного редактирования.nft list set inet filter blacklist4— содержимое набора.nft monitor— поток изменений в живую, удобно видеть, как dynset пополняется.
Если сомневаетесь — применяйте файлы с --check, проверяя синтаксис без внесения изменений: сначала проверка, затем применение. Это дешёвая страховка от опечаток.
Бэкап и персистентность
Держите базовую структуру в основном конфиге (/etc/nftables.conf), а большие наборы подключайте отдельными файлами с include. Обновление выполняйте атомарно: сначала положили новый файл, затем применили его одной командой. Это снизит риск инцидента при деплое и упростит откат.
Миграция с ipset: что нужно знать
IpSet исторически решал ту же задачу, но в связке с iptables. При миграции важно переосмыслить структуру: не переносите один-в-один, а используйте преимущества nftables — семейство inet, interval для CIDR, карты vmap, конкатенированные ключи. Скрипт миграции обычно:
- Экспортирует текущие списки ipset.
- Нормализует и чистит элементы (дубликаты, невалидные строки).
- Формирует
nft-совместимый наборelements = { ... }. - Заливает во временный набор и делает
swap.
Выгода видна сразу: меньше правил, предсказуемая латентность и единый стек управления.
Паттерн безопасного деплоя на проде
- Первым правилом в
input— allowlist для вашего управления. - Правила, использующие наборы, поднимайте выше тяжёлых матчей.
- Все большие изменения — только атомарно через временные наборы и
swap. - Тестируйте конфигурацию на стенде, прогоняйте
--checkперед-f. - Заводите оповещение при срабатывании «автобанов» (dynset), чтобы видеть динамику и не закрыть важный трафик по ошибке.
Частые ловушки и как их избежать
- Случайно забанили себя по широкому CIDR. Всегда держите allowlist выше и делайте dry-run импорта.
- Дубли и «пыль» в списках. Нормализуйте источники, используйте
intervalи предварительную агрегацию префиксов. - Слишком «умные» правила до проверки набора. Переместите обращение к набору выше — это дешевле и надёжнее.
- Разовая массовая очистка через
flush set. На проде лучше «swap» с чистым набором, чтобы не ловить окно между очисткой и догрузкой.
Пример: единый каркас с allowlist, blacklist и dynset
table inet filter {
sets {
allow4 { type ipv4_addr; flags interval; }
allow6 { type ipv6_addr; flags interval; }
blacklist4 { type ipv4_addr; flags interval; }
blacklist6 { type ipv6_addr; flags interval; }
ssh_abusers { type ipv4_addr; timeout 2h; }
}
chain input {
type filter hook input priority 0;
# Управление всегда пропускаем
ip saddr @allow4 accept
ip6 saddr @allow6 accept
# Блоклисты
ip saddr @blacklist4 drop
ip6 saddr @blacklist6 drop
# Динамическая защита SSH
ip saddr @ssh_abusers drop
tcp dport 22 ct state new limit rate over 30/minute add @ssh_abusers { ip saddr timeout 2h } drop
# Базовая политика (пример)
ct state established,related accept
tcp dport { 80, 443 } accept
iif lo accept
icmp type { echo-request, echo-reply, time-exceeded } accept
ip6 nexthdr icmpv6 accept
# Остальное — по вашей политике
# drop или reject
}
}
Сеты и карты в nftables — это современный, быстрый и удобный способ работать с блоклистами. Они позволяют держать огромные списки адресов, добавлять временные баны непосредственно из правил, обновлять их атомарно и при этом сохранять конфигурацию лаконичной. Ключ к успеху — простая структура, ранняя проверка наборов в цепочках, аккуратный импорт и безопасный процесс обновления. С таким подходом фильтрация останется быстрой и предсказуемой даже под высокой нагрузкой.


