Автоматизируем настройку nftables в Linux с помощью универсального Bash-скрипта, используя базовый набор правил для фильтрации сетевого трафика.
Предлагаемый в этой статье скрипт nftables.sh создан под давним впечатлением от статьи на serveradmin.ru, посвященной настройке iptables.
🖐️Эй!
Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике — заглядывайте в Вороний чат @r4ven_me_chat🧐.
Сходу скажу, что это не мануал по работе nftables, а лишь пример использования данной программы для быстрой и удобной настройки базового сетевого экрана в Linux.
Подразумевается, что вы имеете некоторое представление о том, что такое Netfilter и как работают инструменты для его настройки, такие как iptables, nftables, ufw, firewald.
Если вы ранее работали с iptables, но про nftables только слышали: рекомендую полезную вводную статью моего коллеги по цеху: 🔗 Настройка фаервола - Nftables.
☝️Все действия из статьи были протестированы в дистрибутивах Debian 12 и 13.
Предисловие
Я, как и многие другие администраторы Linux систем, немного “проспал” тот момент, когда популярные дистрибутивы перешли на использование nftables в качестве бэкенда по умолчанию для управления сетевым экраном.
Да-да, если вы используете iptables:
sudo iptables -L -nТо с высокой долей вероятности ваши правила уже переведены в nftables:
sudo nft list ruleset📝 Примечание
Разумеется это поведение зависит от дистрибутива и его настроек.
Разработчики постарались сделать переход максимально плавным и создали инструмент для совместимости правил iptables с nftables. На данный момент многие дистрибутивы для команды iptables делают симлинк, указывающий на специальный бинарник nftables.
Например, в Linux Mint Debian Edition 7 команда iptables - это символическая ссылка на утилиту xtables-nft-multi.
Определить конечный файл симлинков можно такой страшной командой:
current="$(command -v iptables)"; while [[ -L "$current" ]]; do target=$(readlink "$current"); echo "$current -> $target"; if [[ "$target" == /* ]]; then current="$target"; else current="$(dirname "$current")/$target"; fi; done; echo "Final: $current"
Сделано это также и для того, чтобы не ломать работу многих сетевых программ в Linux, которые до сих пор используют iptables. Например, фаервол ufw или Docker:

💡К слову, скрипт из статьи предусматривает момент, когда сторонние программы создают свои правила фаервола. Чтобы не нарушать их работу, скрипт взаимодействует с конкретными таблицами. Возможно в вашем случае потребуется некоторая доработка исключений.
Немного про особенности nftables
Чаще всего при прочтении материалов по nftables вы увидите следующую схему:

Она визуализирует работу сетевого экрана Linux - Netfilter и пути прохождения пакетов через него.
Сегодня учить всю схему не нужно, говорить мы будем лишь про семейства IPv4, IPv6 и соответственно IP layer и Application layer. Сюда входят следующие хуки (точки входа, см. схему) фильтрации: prerouting, input, output, forward, postrouting.
Прежде чем приступить к настройке, необходимо уяснить основные сущности современного Linux фаервола:
- Семейства протоколов:
ip(IPv4),ip6(IPv6),inet(IPv4 и IPv6),arp,bridge,netdev; - Хук (hook) - точка входа для пакета относительно семейства протоколов (
prerouting,inputи пр.); - Таблица - контейнер для цепочек, привязан к семейству протоколов;
- Цепочка - контейнер для набора правил, привязан к таблице;
- Приоритет цепочки - определяет порядок обработки правил цепочек в одном хуке (например, в
input); - Правило - одно конкретное условие (
match) и действие (verdict) в цепочке, например, “блокировать с IP 1.2.3.4”; - Выражения - условия в правилах:
ip saddr,tcp dport,ct state,iifnameи т.д. - Действия - что сделать с пакетом:
accept,drop,reject,jump,queue,return
И ключевые различия и нововведения nftables, в сравнении с iptables:
- Единый фреймворк - одна команда
nftвместоiptables,ip6tables,arptables,ebtables, что упрощает управление; - Более читаемый и логичный синтаксис;
- Семейство
inetпозволяет сразу писать одни правила для обоих протоколов IPv4/IPv6; - Динамические наборы (sets) - можно изменять элементы наборов без перезагрузки правил;
- Ассоциативные массивы (maps), которые позволяют сопоставлять “ключ-значение” (например, IP –> действие);
- Действия цепочек могут возвращать значения как функции (
jump,return); - Более эффективная архитектура, особенно при множестве правил;
- Возможность работы с правилами в формате JSON.
Историческая справка для любопытных
ipfwadm -> ipchains -> iptables: Это была эволюция инструментов для фильтрации пакетов в Linux. Каждый следующий был лучше предыдущего, но все они имели одну общую черту: каждый из них был монолитной структурой. То есть код для IPv4 (iptables), IPv6 (ip6tables), для работы с ARP-запросами (arptables) и для фильтрации на уровне кадров Ethernet (ebtables) - это были разные, хоть и похожие, кодовые базы внутри ядра.
Проблемы iptables:
- Дублирование кода: огромное количество похожего кода для IPv4, IPv6 и т.д.;
- Сложность расширений: чтобы добавить новую функциональность, часто приходилось патчить само ядро;
- Несогласованный API: разные инструменты (
iptables,ip6tables) имели разные интерфейсы; - Низкая производительность: при большом количестве правил.
Архитектурный сдвиг: nftables как ядерный бэкенд
Разработчики ядра Linux решили проблему кардинально, разделив архитектуру на две основные части:
Пользовательское пространство (Userspace) - Фронтенд (Frontend):
- Это та команда, которую вы вводите в терминале:
nft; - Её задача - принимать команды и правила от администратора, обрабатывать их и передавать в ядро через специальный API (Netlink);
Пространство ядра (Kernelspace) - Бэкенд (Backend):
- Это и есть сам
nftablesв его основном значении; - Это общий, унифицированный движок (engine) внутри ядра, который выполняет всю “грязную работу” по фильтрации пакетов;
- Он предоставляет общий набор инструкций (виртуальная машина), с помощью которого можно описать практически любое правило для любого сетевого протокола (IPv4, IPv6, ARP, Bridge и т.д.).
Вроде озвучил всё, что хотел. Тема фаервола в Linux очень большая. Трудно охватить всё сразу. Не будем раздувать статью, переходим к скрипту и его описанию.
Скрипт настройки nftables

❗️ Осторожно
Настоятельно прошу вас перед применением правил на production-серверах тщательно проверять их в тестовой среде!
Если настраиваете фаервол удалённо - обязательно убедитесь в доступности консоли сервера с гипервизора.
Материал в данной статье представлен исключительно в образовательных целях. Все действия вы выполняете на свой риск и под свою ответственность. Спасибо за понимание.
Иван Чёрный
Цель написания подобного скрипта: иметь под рукой универсальный инструмент быстрой настройки локального фаервола Linux, с возможностью простой кастомизации набора правил фильтрации под различные нужды. Данный набор составлен в соответствии с принципом “Всё запрещено, кроме явно разрешённого”. Отлично подойдет для защиты хостов, имеющих публичный IP с работающими на нём сервисами.
В скрипте определяется массив NFT_RULES, который содержит команды-правила для nftables. При необходимости отредактируйте/измените/добавьте нужные правила (соблюдая синтаксис и экранируя двойные кавычки внутри правил).
При запуске, скрипт создает временный файл с набором правил в формате nft (его командный вариант), после чего работает уже с ним: проверяет синтаксис/применяет правила. Также у скрипта предусмотрены флаги для бэкапа текущего файла конфигурации (который задаётся в переменной NFT_CONFIG) и сохранения текущего набора правил (nft -s list table inet filter) в этот файл конфигурации (перезаписывая его).
Запускается скрипт от имени root с одним или несколькими параметрами:
-a/--apply- применить правила;-s/--save- сохранить текущие правила (по умолчанию в/etc/nftables.conf);-b/--backup- сделать резервную копию конфигурации (файл рядом с конфигом и именем/etc/nftables.conf_<timestamp>;-c/--check- сформировать временный файл с правилами и проверить синтаксис без применения.
Скрипт призван автоматизировать полную настройку фаервола: создание таблиц, наборов адресов, цепочек и правил фильтрации, включая обработку исключений для интерфейсов контейнеров, виртуализации и VPN. + немного NAT (перенаправление порта и маскарадинг) для примера.
Под спойлером подробное описание логики набора правил из скрипта
ТАБЛИЦА inet filter
Цепочка input - обрабатывает входящие пакеты, направленные в сам хост.
Базовые этапы:
- Отбрасываются некорректные соединения (
ct state invalid drop); - Разрешаются установленные и связанные (
ct state established,related); - Разрешается loopback-трафик (
iif lo accept); - Разрешаются пакеты от доверенных IP;
- Разрешаются ICMP и ICMPv6 (с ограничением скорости);
- Пакеты классифицируются по источнику:
@lan4/@lan6–> переход вinput_lan;- не из LAN –> переход в
input_wan;
- Всё остальное - в
log_drop(логируется и блокируется).
Цепочка input_lan
- Обработка пакетов из внутренних сетей;
- По умолчанию разрешает весь TCP/UDP из LAN (можно ограничить конкретными портами).
Цепочка input_wan
- Контроль входящих соединений из внешней сети (WAN);
- Поведение:
- Разрешает SSH (порт 22);
- Разрешает веб-трафик (80, 443) и базовые UDP-службы (53, 123);
- Настраивает DNAT 443–>43443 (как пример);
- Все остальные новые соединения из WAN - логируются и блокируются.
Цепочка forward
- Управление маршрутизацией между интерфейсами;
- Логика:
- Разрешаются установленные и связанные соединения;
- Разрешаются направления трафика для контейнеров (Docker, K8s), виртуальных машин и VPN-интерфейсов (
cni*,br-*,virbr*,tun*,wg*и др.); - Остальной трафик логируется и блокируется.
Цепочка log_drop
- Журналирование и финальный сброс пакетов;
- Логирует до 5 событий в секунду;
- Отбрасывает все пакеты без разрешающих правил.
ТАБЛИЦА inet nat
Цепочка prerouting
- DNAT до маршрутизации;
- Переадресует входящие соединения с порта 443 на порт 43443 (как пример).
Цепочка postrouting
- SNAT после маршрутизации;
- Выполняет маскарадинг исходящего трафика с VPN-интерфейсов (
tun*, тоже пример).
nvim ./nftables.sh 1#!/usr/bin/env bash
2
3# Script security parameters
4set -Eeuo pipefail
5
6# Explicit PATH definition
7export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
8
9# =============================================================
10# ========== BEGINNING OF USER CONFIGURATION SECTION ==========
11
12NFT="$(command -v nft)"
13NFT_CONFIG="/etc/nftables.conf"
14NFT_RULES=(
15# =====================
16# TABLES & SETS
17# =====================
18# "flush ruleset"
19"add table inet filter"
20"flush table inet filter"
21"add table inet nat"
22"flush table inet nat"
23
24"add set inet filter lan4 { type ipv4_addr; flags interval; elements = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 } }"
25"add set inet filter lan6 { type ipv6_addr; flags interval; elements = { fd00::/8, fe80::/10 } }"
26"add set inet filter trusted { type ipv4_addr; elements = { 123.34.56.78 } }"
27
28# =====================
29# CHAINS
30# =====================
31"add chain inet filter input { type filter hook input priority 0; policy drop; }"
32"add chain inet filter forward { type filter hook forward priority 50; policy drop; }"
33"add chain inet filter output { type filter hook output priority -200; policy accept; }"
34"add chain inet nat prerouting { type nat hook prerouting priority dstnat; policy accept; }"
35"add chain inet nat postrouting { type nat hook postrouting priority srcnat; policy accept; }"
36
37"add chain inet filter input_wan"
38"add chain inet filter input_lan"
39"add chain inet filter log_drop"
40
41# =====================
42# INPUT BASE
43# =====================
44"add rule inet filter input ct state invalid drop comment \"Drop invalid connections\""
45"add rule inet filter input ct state { established, related } accept comment \"Allow established connections\""
46"add rule inet filter input iif lo accept comment \"Allow loopback\""
47"add rule inet filter input ip saddr @trusted accept comment \"Allow trusted IPs\""
48
49# ICMP
50"add rule inet filter input meta l4proto icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 10/second accept comment \"ICMP rate limited\""
51"add rule inet filter input meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, mld-listener-query, mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, mld2-listener-report } accept comment \"Necessary IPv6 ICMP\""
52
53# UDP traceroute
54"add rule inet filter input_wan udp dport 33434-33534 reject comment \"Allow UDP traceroute\""
55
56# LAN/WAN
57"add rule inet filter input ip saddr @lan4 jump input_lan comment \"LAN IPv4 processing\""
58"add rule inet filter input ip6 saddr @lan6 jump input_lan comment \"LAN IPv6 processing\""
59"add rule inet filter input ip saddr != @lan4 jump input_wan comment \"WAN IPv4 processing\""
60"add rule inet filter input ip6 saddr != @lan6 jump input_wan comment \"WAN IPv6 processing\""
61"add rule inet filter input jump log_drop comment \"Default drop\""
62
63# =====================
64# INPUT LAN
65# =====================
66# Allow all
67"add rule inet filter input_lan meta l4proto { tcp, udp } accept comment \"Allow all TCP/UDP from LAN\""
68# Or allow selected and drop others
69# "add rule inet filter input_lan tcp dport { 80, 443 } accept comment \"Allowed TCP ports from LAN\""
70# "add rule inet filter input_lan udp dport { 53, 123 } accept comment \"Allowed UDP ports from LAN\""
71# "add rule inet filter input_lan ct state new jump log_drop comment \"Drop all from LAN with log\""
72
73# =====================
74# INPUT WAN
75# =====================
76"add rule inet filter input_wan tcp dport 22 accept comment \"Allow SSH from WAN\""
77"add rule inet filter input_wan tcp dport { 80, 443 } accept comment \"Allowed TCP ports from WAN\""
78"add rule inet filter input_wan udp dport { 53, 123 } accept comment \"Allowed UDP ports from WAN\""
79"add rule inet filter input_wan iifname \"eth0\" tcp dport 443 accept comment \"DNAT: 443->43443\""
80"add rule inet filter input_wan iifname \"eth0\" ct status dnat tcp dport 43443 accept comment \"DNAT: 443->43443\""
81"add rule inet filter input_wan ct state new jump log_drop comment \"Drop all from WAN\""
82
83# =====================
84# FORWARD
85# =====================
86"add rule inet filter forward ct state established,related accept"
87"add rule inet filter forward iifname \"cni*\" accept comment \"Allow K8s forward in\""
88"add rule inet filter forward oifname \"cni*\" accept comment \"Allow K8s forward out\""
89"add rule inet filter forward iifname \"flannel.*\" accept comment \"Allow K8s forward in\""
90"add rule inet filter forward oifname \"flannel.*\" accept comment \"Allow K8s forward out\""
91"add rule inet filter forward iifname \"vxlan.calico\" accept comment \"Allow K8s forward in\""
92"add rule inet filter forward oifname \"vxlan.calico\" accept comment \"Allow K8s forward out\""
93"add rule inet filter forward iifname \"br-*\" accept comment \"Allow Docker forward in\""
94"add rule inet filter forward oifname \"br-*\" accept comment \"Allow Docker forward out\""
95"add rule inet filter forward iifname \"virbr*\" accept comment \"Allow VMs forward in\""
96"add rule inet filter forward oifname \"virbr*\" accept comment \"Allow VMs forward out\""
97"add rule inet filter forward iifname \"tun*\" accept comment \"Allow OC forward in\""
98"add rule inet filter forward oifname \"tun*\" accept comment \"Allow OC forward out\""
99"add rule inet filter forward iifname \"wg*\" accept comment \"Allow WG forward in\""
100"add rule inet filter forward oifname \"wg*\" accept comment \"Allow WG forward out\""
101"add rule inet filter forward ct state new jump log_drop comment \"Drop all forward\""
102
103# =====================
104# LOG & DROP
105# =====================
106"add rule inet filter log_drop limit rate 5/second log prefix \"NFT-DROP: \" flags all counter comment \"Drop logging\""
107# "add rule inet filter input meta l4proto tcp reject with tcp reset comment \"Reject TCP\""
108# "add rule inet filter input meta l4proto udp reject comment \"Reject UDP\""
109# "add rule inet filter input counter reject with icmpx type port-unreachable comment \"Reject other protocols\""
110# "add rule inet filter input pkttype host limit rate 5/second counter reject with icmpx type admin-prohibited comment \"Protection from port scanning\""
111"add rule inet filter log_drop drop comment \"Drop all\""
112
113# =====================
114# NAT
115# =====================
116"add rule inet nat prerouting iifname \"eth0\" tcp dport 443 redirect to 43443 comment \"DNAT: 443->43443\""
117# "add rule inet nat prerouting iifname \"eth0\" tcp dport 443 dnat to :43443 comment \"DNAT: 443->43443 \""
118# "add rule inet nat postrouting oifname != lo masquerade comment \"SNAT: NAT processing for all\""
119# "add rule inet nat postrouting oifname \"eth0\" masquerade comment \"SNAT: NAT procesing for eth0\""
120"add rule inet nat postrouting oifname \"tun*\" masquerade comment \"SNAT: NAT procesing for VPN\""
121)
122
123# ========== END OF USER CONFIGURATION SECTION ==========
124# =======================================================
125
126SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd -P)
127SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
128SCRIPT_TMP=$(mktemp "${SCRIPT_DIR}"/"${SCRIPT_NAME}"_XXXXXXXX)
129
130cleanup() {
131 trap - SIGINT SIGTERM SIGHUP SIGQUIT ERR EXIT
132
133 rm -f "$SCRIPT_TMP"
134}
135
136trap cleanup SIGINT SIGTERM SIGHUP SIGQUIT ERR EXIT
137
138
139usage() {
140 cat <<EOF
141Usage: $SCRIPT_NAME OPTIONS
142
143Description:
144 Script to configure the firewall via nftables.
145
146Options:
147 -h, --help Show this help message and exit
148 -a, --apply *Apply new rules from this script
149 -s, --save *Save current ruleset to /etc/nftables.conf
150 -b, --backup *Create a backup of /etc/nftables.conf
151 -c, --check *Dry-run mode (check syntax without applying changes)
152
153Note:
154 One of the * options is required.
155
156Examples:
157 nftables.sh -a
158 nftables.sh --check --apply
159 nftables.sh --apply --save --backup
160EOF
161}
162
163
164parse_params() {
165 APPLY=0 SAVE=0 BACKUP=0 CHECK=0
166
167 while [[ $# -gt 0 ]]; do
168 case $1 in
169 -h|--help) usage; exit 0 ;;
170 -a|--apply) APPLY=1 ;;
171 -s|--save) SAVE=1 ;;
172 -b|--backup) BACKUP=1 ;;
173 -c|--check) CHECK=1 ;;
174 *) usage; exit 1 ;;
175 esac
176 shift
177 done
178
179 (( APPLY || SAVE || BACKUP || CHECK )) || { usage; exit 1; }
180
181 # echo "apply=$APPLY save=$SAVE backup=$BACKUP"
182}
183
184
185save_current_rules() {
186 echo -e "#!${NFT} -f\n\nflush ruleset\n\n$($NFT -s list table inet filter)" > "$NFT_CONFIG"
187 chmod 644 "$NFT_CONFIG"
188 echo "Rules successfully saved at $NFT_CONFIG"
189}
190
191
192backup_current_config() {
193 local datetime
194 datetime="$(date +%Y-%m-%d_%H-%M-%S)"
195
196 if [[ -f "$NFT_CONFIG" ]]; then
197 cp "${NFT_CONFIG}"{,_"${datetime}"}
198 echo "Backup created: ${NFT_CONFIG}_${datetime}"
199 fi
200}
201
202
203# =====================
204# Main script flow
205# =====================
206parse_params "$@"
207
208# Check for root privileges
209if [[ $EUID -ne 0 ]]; then
210 echo "Please run as root"
211 exit 1
212fi
213
214if (( BACKUP )); then backup_current_config; fi
215
216if (( CHECK )); then
217 for rule in "${NFT_RULES[@]}"; do
218 echo "$rule" >> "$SCRIPT_TMP"
219 done
220
221 "$NFT" -c -f "$SCRIPT_TMP"|| { echo "Syntax - error"; exit 1; }
222 cat "$SCRIPT_TMP"
223 echo -e "-----------\nSyntax - ok"
224fi
225
226if (( APPLY )); then
227 for rule in "${NFT_RULES[@]}"; do
228 echo "$rule" >> "$SCRIPT_TMP"
229 done
230
231 if ! (( CHECK )); then
232 "$NFT" -c -f "$SCRIPT_TMP" || { echo "Syntax - error"; exit 1; }
233 fi
234
235 "$NFT" -f "$SCRIPT_TMP" && echo "Rules applied"
236fi
237
238if (( SAVE )); then save_current_rules; fi⚠️Предупреждение
Обязательно замените имя основного интерфейса в правилах eth0 на своё. Пример:
sed -i 's/eth0/enp0s5/g' ./nftables.sh📝Обратите внимание, что nftables позволяет задавать комментарии для правил прямо в самих правилах. Параметр comment👍.
Разбор правил по порядку:
Таблицы и наборы (sets)
add table inet filterСоздает таблицу filter для фильтрации пакетов. Имя таблицы произвольное.
flush table inet filterОчищает таблицу filter перед добавлением новых правил.
add table inet natСоздает таблицу nat для NAT-трансляции.
flush table inet natОчищает таблицу nat.
add set inet filter lan4 { type ipv4_addr; flags interval; elements = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 } }Создает набор IPv4-сетей LAN.
add set inet filter lan6 { type ipv6_addr; flags interval; elements = { fd00::/8, fe80::/10 } }Создает набор IPv6-сетей LAN.
add set inet filter trusted { type ipv4_addr; elements = { 123.34.56.78 } }Определяет список доверенных IP-адресов.
Цепочки (chains)
add chain inet filter input { type filter hook input priority 0; policy drop; }Создает цепочку input с политикой DROP по умолчанию.
add chain inet filter forward { type filter hook forward priority 50; policy drop; }Создает цепочку forward с политикой DROP.
add chain inet filter output { type filter hook output priority -200; policy accept; }Создает цепочку output, разрешающую исходящие соединения.
add chain inet nat prerouting { type nat hook prerouting priority dstnat; policy accept; }Создает цепочку prerouting для входящего NAT (DNAT).
add chain inet nat postrouting { type nat hook postrouting priority srcnat; policy accept; }Создает цепочку postrouting для исходящего NAT (SNAT).
add chain inet filter input_wan
add chain inet filter input_lan
add chain inet filter log_dropСоздает пользовательские цепочки для разделения трафика и логирования.
Базовые правила INPUT
add rule inet filter input ct state invalid dropОтбрасывает некорректные соединения.
add rule inet filter input ct state { established, related } acceptРазрешает установленные и связанные соединения (механизм contrack).
add rule inet filter input iif lo acceptРазрешает локальный трафик (loopback).
add rule inet filter input ip saddr @trusted acceptСразу разрешает доступ доверенным IP.
ICMP
add rule inet filter input meta l4proto icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 10/second acceptРазрешает основные типы ICMP IPv4 с ограничением по скорости (небольшая защита от флуда и DoS атак).
add rule inet filter input meta l4proto ipv6-icmp icmpv6 type { ... } acceptРазрешает необходимые ICMPv6-пакеты (ND, MLD и др.).
За блок про ICMP спасибо пользователю Comm из Вороньего чата.
Traceroute
add rule inet filter input_wan udp dport 33434-33534 rejectРазрешает UDP-traceroute (через reject уведомления).
Разделение LAN/WAN
add rule inet filter input ip saddr @lan4 jump input_lan
add rule inet filter input ip6 saddr @lan6 jump input_lanПередает трафик из LAN в цепочку input_lan.
add rule inet filter input ip saddr != @lan4 jump input_wan
add rule inet filter input ip6 saddr != @lan6 jump input_wanПередает внешний трафик в цепочку input_wan.
add rule inet filter input jump log_dropПередает оставшийся трафик на логирование и блокировку.
Цепочка INPUT LAN
add rule inet filter input_lan meta l4proto { tcp, udp } acceptРазрешает весь TCP/UDP-трафик из LAN.
Альтернативно, можно разрешить выборочные порты
add rule inet filter input_lan tcp dport { 80, 443 } accept
add rule inet filter input_lan udp dport { 53, 123 } accept
add rule inet filter input_lan ct state new jump log_dropЗакоментировав разрешающее правило:
add rule inet filter input_lan meta l4proto { tcp, udp } acceptЦепочка INPUT WAN
add rule inet filter input_wan tcp dport 22 limit rate 20/minute acceptРазрешает SSH.
add rule inet filter input_wan tcp dport { 80, 443 } acceptРазрешает HTTP(S).
add rule inet filter input_wan udp dport { 53, 123 } acceptРазрешает DNS и NTP.
add rule inet filter input_wan iifname "eth0" tcp dport 443 accept
add rule inet filter input_wan iifname "eth0" ct status dnat tcp dport 43443 acceptОбрабатывает DNAT-переадресацию порта 443 на порт 43443 - в хуке input. Для корректной работы требует также правило в хуке forward (см. ниже).
add rule inet filter input_wan ct state new jump logdropБлокирует новые неразрешенные соединения из WAN.
Цепочка FORWARD
add rule inet filter forward ct state established,related acceptРазрешает установленные соединения.
add rule inet filter forward iifname "cni*" accept
add rule inet filter forward oifname "cni*" acceptРазрешает маршрутизацию для Kubernetes.
add rule inet filter forward iifname "flannel.*" accept
add rule inet filter forward oifname "flannel.*" acceptРазрешает трафик через Flannel.
add rule inet filter forward iifname "vxlan.calico" accept
add rule inet filter forward oifname "vxlan.calico" acceptРазрешает трафик Calico.
add rule inet filter forward iifname "br-*" accept
add rule inet filter forward oifname "br-*" acceptРазрешает Docker-сети.
add rule inet filter forward iifname "virbr*" accept
add rule inet filter forward oifname "virbr*" acceptРазрешает трафик виртуальных машин.
add rule inet filter forward iifname "tun*" accept
add rule inet filter forward oifname "tun*" accept
add rule inet filter forward iifname "wg*" accept
add rule inet filter forward oifname "wg*" acceptРазрешает трафик VPN (OpenConnect, WireGuard).
add rule inet filter forward ct state new jump log_dropБлокирует неизвестный форвард.
Логирование и дроп
add rule inet filter log_drop limit rate 5/second log prefix "NFT-DROP:" flags all counterВключает логирование отброшенных пакетов с префиксом “NFT-DROP:”, ограничение скорости логирования до 5 пакетов в секунду и подсчет количества отброшенных пакетов. flags all указывает логировать все флаги пакетов.
add rule inet filter log_drop dropОкончательно блокирует весь оставшийся трафик.
NAT
add rule inet nat prerouting iifname "eth0" tcp dport 443 redirect to 43443Переадресует порт 443 на 43443 (DNAT) - для работы требует разрешающее правило в хуке input (см. выше).
add rule inet nat postrouting oifname "tun*" masqueradeВыполняет SNAT (маскарадинг) для VPN-интерфейсов.
Короткое резюме правил:
- Скрипт создает изолированные таблицы
filterиnat, полностью сбрасывая старые правила; - Входящие соединения разрешаются только из доверенных сетей, LAN или для конкретных портов WAN;
- Применяется строгая политика по умолчанию:
dropдля входящих и форвардных цепочек; - Реализовано ограничение для ICMP;
- Разрешается маршрутизация для Docker, KVM, Kubernetes и VPN (WireGuard, OpenConnect);
- Все неразрешенные пакеты логируются и блокируются;
- Настроен NAT для переадресации и маскарадинга VPN (интерфейсы
tun*).
Демонстрация работы скрипта nftables.sh
Если nftables еще не установлен в вашей системе, устанавливаем
sudo apt update && sudo apt install -y nftablesДелаем скрипт исполняемым:
chmod +x ./nftables.sh
./nftables.sh --helpПроверяем синтаксис правил:
☝️Для работы со скриптом требуются права суперпользователя, например, через sudo.
sudo ./nftables.sh -c
Как добропорядочные пользователи делаем бэкап текущей конфигурации:
☝️Ркомендуется делать перед каждым изменением рабочей конфигурации.
sudo ./nftables.sh -b
Применяем правила:
sudo ./nftables.sh -a
Смотрим список всех правил nftables:
sudo nft list ruleset
Проверяем текущую сессию, не дропнуло ли наш SSH:
w
date
Теперь открываем соседнюю вкладку и проверяем, что можем подключиться к серверу:

Пока всё ок. Проверяем работу фаервола дальше.
☝️В случае необходимости отката изменений, выполните:
sudo nft delete table inet filter
sudo nft delete table inet nat
sudo nft -f /etc/nftables.conf_<timestamp>ICMP - выполняем с клиентской машины:
ping -c3 nftables.r4ven.meТрассировка (UDP):
traceroute nftables.r4ven.me
Всё проходит.
Теперь проверим блокировку ICMP свише 10 пакетов в секунду.
В наборе правил есть одно для логирования: оно пишет события в системный журнал.
На сервере смотрим drop журнал с фильтрацией по ICMP:
sudo journalctl -k -f -g 'NFT-DROP' -g 'ICMP'📝Эта команда использует journalctl для просмотра системного журнала ядра (-k) в реальном времени (-f). Она фильтрует записи, содержащие одновременно слова “NFT-DROP” и “ICMP” (-g 'NFT-DROP' -g 'ICMP').
На клиенте запускаем 20 паралельных ping:
seq 20 | xargs -n1 -P20 sh -c 'ping -c5 nftables.r4ven.me'В логе будут видны сообщения о дропах:

А у клиента будут видны потери пакетов:

Для следующих тестов на клиенте и на сервере установим утилиту для работы с сетевыми соединениями - socat:
sudo apt update && sudo apt install -y socatТеперь с её помощью на хосте с настроенным фаерволом запустим TCP сервер на 443 порту, доступ к которому мы разрешили при настройке фаервола.
На сервере запускаем процесс, который при получении TCP пакета отправит pong:
sudo socat -v TCP-LISTEN:443,fork SYSTEM:"echo 'pong'"С клиента отправляем тестовый запрос:
echo "ping" | socat - TCP:nftables.r4ven.me:443
Работает. Теперь проверим какой нибудь не открытый порт.
На сервере:
sudo socat -v TCP-LISTEN:444,fork SYSTEM:"echo 'pong'"На клиенте:
echo "ping" | socat - TCP:nftables.r4ven.me:444
Как видим, соединение не устанавливается.
Смотрим дроп лог nftables:
sudo journalctl -k -f -g 'NFT-DROP' -g '12.34.56.78'Где 12.34.56.78 - ваш внешний IP из-под которого вы выполняете клиентское подключение.
💡Узнать свой внешний IP можно такой командой с клиента:
curl eth0.me
Отлично, фаервол работает ожидаемо😅.
Теперь проверим работу перенаправления портов. В наших правилах настраивался редирект 443 порта в хуке prerouting на порт 43443 (и разрешающее правило в хуке input).
На сервере слушаем 43443 TCP порт:
sudo socat -v TCP-LISTEN:43443,fork SYSTEM:"echo 'pong'"На клиенте отправляем запрос на 443 TCP порт:
echo "ping" | socat - TCP:nftables.r4ven.me:443
Работет!
Подобным образом проверим работу UDP. В наших правилах мы открывали 123 порт.
Выполняем на сервере:
sudo socat -v UDP-RECVFROM:123,fork SYSTEM:"echo 'pong'"А на клиенте:
echo "ping" | socat - UDP:nftables.r4ven.me:123
Всё хорошо. Также проверим drop неразрешённых UDP:
На сервере:
sudo socat -v UDP-RECVFROM:12345,fork SYSTEM:"echo 'pong'"На клиенте:
echo "ping" | socat - UDP:nftables.r4ven.me:12345
Пусто. Зато при просмотре журнала:
sudo journalctl -k -f -g 'NFT-DROP' -g '12345'Видим соответствующие записи:

После того, как точно убиделись в корректной настройки фаервола, сохраняем правила:
sudo ./nftables.sh -s
cat /etc/nftables.conf
💡 Совет
Для обновления правил отредактируйте массив NFT_RULES и примените изменения:
sudo ./nftables.sh --backup --apply --saveНастройка автостарта nftables при запуске OS
В состав пакета nftables идёт юнит сервиса Systemd, который по сути запускает команду с применение правил из основного файла конфигурации: nft -f /etc/nftables.conf (в deb-based дистрибутивах, в rpm-based - /etc/sysconfig/nftables.conf).
Включаем автозапуск:
sudo systemctl enable --now nftables
sudo systemctl status nftables
sudo nft list ruleset
Для полного спокойствия и проверки работы фаервола рекомендуется выполнить профилактический перезапуск сервера:
⚠️ Не забудьте про доступ с гипервизора.
sudo rebootПолезные команды nftables
# Показать весь набор правил
sudo nft list ruleset
# Показать все таблицы
sudo nft list tables
# Показать содержимое таблицы filter
sudo nft list table inet filter
# Показать цепочку input таблицы filter
sudo nft list chain inet filter input
# Показать правила с handle - порядковые номера правил (для замены/удаления)
sudo nft -a list ruleset
# Очистить все правила
sudo nft flush ruleset
# Очистить только таблицу filter (ОСТОРОЖНО! Не удаляет политики по умолчанию)
sudo nft flush table inet filter
# Удалить таблицу filter
sudo nft delete table inet filter
# Добавить правило (пример)
sudo nft add rule inet filter input tcp dport 22 accept
# Показать цепочку с handle
sudo nft -a list chain inet filter input
# Удалить правило по handle
sudo nft delete rule inet filter input handle 15
# Заменить правило по handle
sudo nft replace rule inet filter input handle 15 tcp dport 2222 accept
# Загрузить конфиг из файла
sudo nft -f /etc/nftables.conf
# Проверить синтаксис без применения
sudo nft -c -f /etc/nftables.conf
# Сохранить текущие правила в конфиг
sudo nft -s list ruleset > /etc/nftables.conf
# Отслеживать изменения в реальном времени
sudo nft monitor
# Трассировка прохождения пакетов
sudo nft monitor trace
# Подробный отладочный вывод при загрузке
sudo nft --debug=netlink -f myrules.nft
# Просмотре журнала nftables
sudo journalctl -k -f -g 'NFT-DROP'Послесловие
Данный скрипт я протестировал на нескольких локальных и публичных (с внешним IP) серверах. Пока полёт нормальный, но возможно где-то могут быть недочёты (я не сетевик😑). Еще раз напомню, что все действия вы делаете на свой страх и риск. Если у вас остались вопросы по теме, то можете задать их в нашем Вороньем чате. В свободное время я постараюсь вам ответить👨💻.
Спасибо, что читаете!


