Правило вида iptables -A INPUT -s 1.2.3.4 -j DROP живёт в оперативной памяти ядра и исчезает при первой же перезагрузке. Так работают все подсистемы netfilter: ни iptables, ни nftables, ни ipset не записывают своё состояние на диск. Блокировка переживёт reboot только при явной настройке персистентности.
Рабочих схем четыре. Для iptables это iptables-save с iptables-restore либо пакет iptables-persistent, который ставит сервис netfilter-persistent. Для nftables - файл /etc/nftables.conf и юнит nftables.service. Для ipset - сохранение наборов через ipset save и загрузка через ipset restore. Fail2ban в отдельной настройке не нуждается: баны лежат в SQLite-базе /var/lib/fail2ban/fail2ban.sqlite3 и восстанавливаются при старте сервиса.
Ниже - команды и конфиги для каждого инструмента, порядок загрузки, проверка после reboot и разбор ошибок, из-за которых защита отключается ровно в момент перезагрузки.
Почему блокировки IP сбрасываются после перезагрузки
Правила и наборы IP хранятся в памяти ядра, диска у них нет. Перезагрузка пересоздаёт сетевой стек с нуля: таблицы filter, nat, mangle, а также наборы ipset оказываются пустыми. Счётчики пакетов и байтов обнуляются вместе с ними.
systemd правила фильтрации сам не восстанавливает. В multi-user.target нет юнита, который читал бы /etc/iptables/rules.v4 или /etc/nftables.conf, если вы не установили пакет персистентности и не создали юнит вручную. Поэтому на свежем сервере после чистого reboot команда iptables -S показывает только политики цепочек, а nft list ruleset отдаёт пустой вывод.
Fail2ban устроен иначе: он сервис с состоянием. Баны и история совпадений лежат в базе SQLite, а не только в ядре. При запуске fail2ban.service читает базу, отбирает активные баны и заново выполняет для них действие actionban. Вручную добавленное правило такой истории не имеет нигде, поэтому исчезает.
Разница между ручными правилами и автоматическими банами
Сценарий: вы видите перебор паролей по SSH и блокируете адрес руками командой iptables -I INPUT -s 1.2.3.4 -j DROP. Правило работает до перезагрузки. После reboot адрес снова пройдёт в порт 22, потому что история блокировки нигде не сохранена.
Если тот же адрес забанен через Fail2ban, картина другая. Jail фиксирует событие, пишет запись в базу и вызывает banaction, который добавляет правило в iptables, nftables или ipset. При следующем старте сервис снова читает базу и применяет каждый действующий бан. Блокировка возвращается без вашего участия.
| Инструмент | Где хранится состояние | Что будет после reboot без настройки | Чем сохранить |
|---|---|---|---|
| iptables | память ядра | правила исчезнут | iptables-save + iptables-restore либо netfilter-persistent |
| nftables | память ядра | ruleset пустой | дамп ruleset в /etc/nftables.conf + nftables.service |
| ipset | память ядра | наборы пустые | ipset save + ipset restore |
| Fail2ban | SQLite в /var/lib/fail2ban/ | баны восстановятся сами | systemctl enable fail2ban |
Вывод простой: для iptables, nftables и ipset персистентность включаете вы сами, для Fail2ban достаточно работающего автозапуска сервиса.
Сохранение правил iptables после перезагрузки
iptables отдаёт текущее состояние в текстовом виде, и его достаточно перенаправить в файл. Перед этим уточните бэкенд: команда iptables --version покажет (legacy) или (nf_tables). От этого зависит, как правила лягут в ядро и не конфликтуют ли они с nftables. Подробный разбор различий между iptables, nftables и ufw с примерами правил есть в отдельном материале о настройке файрвола в Linux.
Ручное сохранение через iptables-save и iptables-restore
Шаг 1. Создайте каталог и сохраните текущие правила, включая IPv6:
mkdir -p /etc/iptables iptables-save > /etc/iptables/rules.v4 ip6tables-save > /etc/iptables/rules.v6 head -n 20 /etc/iptables/rules.v4
Шаг 2. Проверьте файл перед применением. Ключ --test разбирает синтаксис и ничего не меняет в ядре:
iptables-restore --test < /etc/iptables/rules.v4
Шаг 3. Восстановите правила вручную, чтобы убедиться, что файл рабочий:
iptables-restore < /etc/iptables/rules.v4 ip6tables-restore < /etc/iptables/rules.v6
iptables-restore по умолчанию заменяет таблицы целиком: сначала очищает их, затем загружает содержимое файла. Ключ -n (--noflush) отключает очистку и добавляет правила к существующим. Учтите: атомарность загрузки и поведение ключа --test в официальной документации iptables-restore явно не описаны, поэтому проверяйте такие сценарии на тестовом стенде.
Шаг 4. Добавьте восстановление в автозагрузку. Положите юнит в /etc/systemd/system/restore-iptables.service:
[Unit] Description=Restore iptables rules After=network-pre.target Wants=network-pre.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/bin/sh -c '/sbin/iptables-restore < /etc/iptables/rules.v4 && /sbin/ip6tables-restore < /etc/iptables/rules.v6' [Install] WantedBy=multi-user.target
Дальше стандартные три команды: systemctl daemon-reload, systemctl enable --now restore-iptables.service, systemctl status restore-iptables.service. На старых системах без systemd ту же цепочку команд прописывают в /etc/rc.local. Для IPv6 всегда держите отдельный файл rules.v6 и вызов ip6tables-restore, иначе адреса блокируются только по IPv4.
Автоматизация через netfilter-persistent
В Debian и Ubuntu ставится пакет iptables-persistent, который создаёт сервис netfilter-persistent:
apt install iptables-persistent systemctl status netfilter-persistent netfilter-persistent save netfilter-persistent reload
При установке debconf спросит, сохранить ли текущие правила. Ответ Yes перенесёт их в /etc/iptables/rules.v4 и /etc/iptables/rules.v6, а сервис сразу включится в автозагрузку. netfilter-persistent использует набор плагинов для загрузки, очистки и сохранения правил netfilter при загрузке и остановке системы; плагины лежат в /usr/share/netfilter-persistent/plugins.d/, список смотрите через ls -l /usr/share/netfilter-persistent/plugins.d/. Команда netfilter-persistent save вызывает все плагины с аргументом save, чтобы записать текущие загруженные правила в постоянное хранилище.
В RHEL, CentOS и AlmaLinux аналог - пакет iptables-services с юнитом iptables.service и файлом /etc/sysconfig/iptables. Сохранение: iptables-save > /etc/sysconfig/iptables, автозапуск: systemctl enable iptables. Для IPv6 используется /etc/sysconfig/ip6tables.
Если iptables собран поверх nf_tables, netfilter-persistent продолжает работать, но смешивать его с правками через nft не стоит: два инструмента пишут в один и тот же ruleset и легко перетирают друг друга. Выберите один путь.
Персистентность nftables через nftables.conf
nftables загружает состояние из файла, который читает юнит nftables.service при старте. В Debian и RHEL это /etc/nftables.conf. Синтаксис внутри такой же, как у команд nft в консоли.
Сохранение текущих правил nftables в файл
Дамп делает команда nft list ruleset, но готовый текст к загрузке не годится: в нём нет строки flush ruleset, поэтому при повторном применении правила добавляются к текущей конфигурации, не перезаписывая её полностью, что может привести к проблемам. Собирайте файл так:
{
echo '#!/usr/sbin/nft -f'
echo 'flush ruleset'
nft list ruleset
} > /etc/nftables.conf
nft -c -f /etc/nftables.conf
Ключ -c проверяет корректность команд без фактического применения изменений. Рабочий пример с блокировкой отдельных адресов и набора:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set blacklist {
type ipv4_addr
flags interval
timeout 1d
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
tcp dport 22 accept
tcp dport { 80, 443 } accept
ip saddr @blacklist drop
ip saddr 203.0.113.7 drop
}
}
Набор с параметром timeout удобен для временных блокировок: адрес выпадает из сета сам, без отдельной команды. Дополнять набор в рантайме можно через nft add element inet filter blacklist { 198.51.100.9 }, а проверять - через nft list set inet filter blacklist. Больше приёмов с динамическими наборами, rate limiting через meter и авто-блокировкой по таймауту собрано в гайде по динамическим наборам nftables.
Автозагрузка через systemd-сервис nftables
Проверьте юнит и включите его:
systemctl status nftables systemctl enable --now nftables systemctl cat nftables
Команда systemctl cat покажет фактический путь к конфигу в ExecStart, обычно это /usr/sbin/nft -f /etc/nftables.conf. Если юнита в дистрибутиве нет, создайте свой по образцу из раздела про systemd ниже, заменив ExecStart на /usr/sbin/nft -f /etc/nftables.conf. Правки, сделанные в рантайме через nft add, в файл не попадают: перед перезагрузкой их нужно сохранить повторным дампом, иначе после reboot их не будет.
Сохранение наборов ipset после перезагрузки
Наборы ipset живут в памяти ядра и восстанавливаться должны раньше правил iptables или nftables, которые на них ссылаются. Если порядок обратный, загрузка правил прерывается сообщением вида Set blacklist doesn't exist, и часть цепочки не применяется.
Ручное сохранение и восстановление наборов ipset
ipset create blacklist hash:ip timeout 86400 ipset add blacklist 1.2.3.4 ipset add blacklist 198.51.100.0/24 ipset save > /etc/ipset.conf ipset test blacklist 1.2.3.4
Файл /etc/ipset.conf содержит строки create и add. Восстановление выполняется одной командой, а ключ -exist гасит ошибки о уже существующих наборах и элементах:
ipset restore -exist -f /etc/ipset.conf ipset list blacklist
Отдельный набор можно выгрузить точечно: ipset save blacklist > /etc/ipset-blacklist.conf. Без ключа -exist повторный restore на существующий набор вернёт ошибку Set cannot be created: it already exists, хотя остальные строки применятся.
Автоматизация через ipset-persistent или systemd-юнит
В Debian и Ubuntu существует плагин netfilter-persistent для ipset, который делает наборы постоянными, в том числе при перезагрузке. Плагин кладут в /usr/share/netfilter-persistent/plugins.d/ и делают исполняемым. После установки проверьте, что плагин виден в этом каталоге. Ручное сохранение - та же команда netfilter-persistent save, она обходит все плагины по очереди. Точный путь к файлу с наборами зависит от реализации плагина, поэтому сверяйтесь с его содержимым, а не с предположениями.
Если пакета в репозитории нет, соберите отдельный юнит /etc/systemd/system/restore-ipset.service:
[Unit] Description=Restore ipset sets After=network.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/sbin/ipset restore -exist -f /etc/ipset.conf [Install] WantedBy=multi-user.target
В RHEL и CentOS наборы закрывает пакет ipset-service: сохранить через ipset save > /etc/sysconfig/ipset, включить через systemctl enable ipset. Точный путь к файлу и имя юнита уточняйте в документации вашего дистрибутива. Порядок загрузки задаётся зависимостями: юнит с правилами iptables получает After=ipset.service и Requires=ipset.service, тогда наборы гарантированно существуют к моменту загрузки цепочек.
Почему Fail2ban автоматически восстанавливает баны
Fail2ban хранит баны в базе /var/lib/fail2ban/fail2ban.sqlite3 и при старте применяет их заново. Никаких дополнительных файлов с правилами создавать не нужно, достаточно, чтобы сервис был включён в автозагрузку.
Как Fail2ban хранит и восстанавливает баны
Последовательность такая. Фильтр ловит строки в логе (SSH-логи в /var/log/auth.log или в journal), jail накапливает совпадения, по достижении maxretry добавляет бан в базу и вызывает banaction. Для jail sshd действие по умолчанию - iptables-multiport, nftables-multiport или вариант с ipset. В nftables-действии адрес попадает в набор, созданный fail2ban, и учитывается правилом с таймаутом.
При загрузке системы systemd запускает fail2ban.service, а тот читает базу, отбирает записи с неистёкшим сроком и повторно выполняет banaction для каждой. Файл базы по умолчанию - /var/lib/fail2ban/fail2ban.sqlite3: в нём хранятся постоянные данные, которые позволяют восстанавливать баны и продолжать чтение логов с последней позиции при перезапуске fail2ban. Сколько записей хранится в базе, задаёт параметр dbpurgeage в fail2ban.conf: он определяет возраст (в секундах), при достижении которого баны удаляются из базы, значение по умолчанию - 86400 (24 часа). Проверить результат можно командами fail2ban-client status, fail2ban-client status sshd и fail2ban-client get sshd banip, а наличие правил в ядре - через nft list ruleset с поиском по f2b или iptables -L -n с фильтром по f2b.
Базовая схема защиты с политикой deny all, связкой nftables и Fail2ban и аудитом правил разобрана в руководстве по настройке файрвола на Linux-сервере.
Настройка автозапуска Fail2ban
systemctl is-enabled fail2ban systemctl enable --now fail2ban fail2ban-client -t journalctl -u fail2ban -b
Что ломает восстановление банов: сервис выключен из автозагрузки; конфиг с синтаксической ошибкой, из-за которой демон не стартует (проверка - fail2ban-client -t); удалённая или повреждённая база; переопределённое в jail.local действие, которого нет на этом хосте. Файл базы копируется только при остановленном сервисе, иначе снимок будет несогласованным. Учтите и dbpurgeage: при значении по умолчанию 86400 секунд (24 часа) баны старше суток в базе не хранятся, и после перезагрузки они не вернутся, даже если формально не истекли.
Настройка автозагрузки через systemd-юнит
Штатные пакеты закрывают большинство сценариев, но иногда нужен один юнит: например, ipset и iptables должны примениться строго по порядку, или файлы лежат в нестандартных путях.
Пример systemd-юнита для восстановления правил
[Unit] Description=Restore ipset and iptables rules After=network-pre.target Wants=network-pre.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/bin/sh -c '/sbin/ipset restore -exist -f /etc/ipset.conf && /sbin/iptables-restore < /etc/iptables/rules.v4' [Install] WantedBy=multi-user.target
systemctl daemon-reload systemctl enable --now restore-firewall.service systemctl status restore-firewall.service journalctl -u restore-firewall -b systemctl --failed
Разбор параметров. Type=oneshot подходит для скриптов, которые отрабатывают один раз и завершаются. RemainAfterExit=yes оставляет юнит в состоянии active (exited), поэтому зависимости видят его запущенным, а systemctl status показывает результат. В ExecStart указывайте абсолютные пути: systemd не ищет бинарники в PATH. Если команда вернёт ненулевой код, юнит перейдёт в failed, и это сразу видно в systemctl --failed.
Порядок загрузки и зависимости
Правило iptables с -m set --match-set blacklist ссылается на набор ipset, поэтому набор должен существовать раньше. В systemd порядок задают After= и Requires=:
[Unit] Description=Restore iptables rules After=network-pre.target ipset.service Requires=ipset.service
Если наборы грузит ваш же юнит, объедините команды в одном ExecStart через двойной амперсанд, как в примере выше: так не появится окно, когда правила уже есть, а наборов ещё нет. Для правил, зависящих от DNS или внешних ресурсов, добавьте After=network-online.target и Wants=network-online.target; для обычных блокировок по адресам это не требуется.
Проверка и отладка после перезагрузки
Проверять нужно живое состояние ядра после reboot, а не содержимое конфигов. Ниже набор команд, который закрывает все четыре инструмента.
Команды для проверки правил и банов
iptables -S iptables -L INPUT -n -v --line-numbers iptables -C INPUT -s 1.2.3.4 -j DROP; echo $? nft list ruleset nft -a list ruleset nft list set inet filter blacklist ipset list ipset test blacklist 1.2.3.4 fail2ban-client status fail2ban-client status sshd fail2ban-client get sshd banip systemctl is-enabled netfilter-persistent nftables ipset-persistent fail2ban
iptables -C возвращает код 0, если правило существует, и 1, если его нет; вывода при этом не будет, поэтому код проверяют через echo $?. ipset test печатает, входит ли адрес в набор. Ключ nft -a добавляет handles, по которым удобно удалять конкретное правило. Логи по загрузке правил смотрите так:
journalctl -b -u netfilter-persistent journalctl -b -u nftables journalctl -b -u fail2ban journalctl -b | grep -i ipset
Ключ -b ограничивает вывод текущей загрузкой, иначе в логах легко утонуть.
Типичные ошибки и как их исправить
- Правила сохранены, но сервис выключен: systemctl is-enabled показывает disabled. Лечится одной командой systemctl enable --now.
- Правила ушли не в тот файл. Сервис читает /etc/iptables/rules.v4 или /etc/nftables.conf, а вы писали в /etc/iptables.rules. Смотрите фактические пути: systemctl cat nftables, systemctl cat netfilter-persistent.
- Смешаны бэкенды. iptables --version показывает (nf_tables) или (legacy), выбор подтверждает update-alternatives --display iptables. Если iptables собран поверх nf_tables, а правила вы ведёте ещё и через nft, iptables-save отдаст только то, что удалось перевести в iptables-синтаксис. Держите один инструмент.
- ipset восстановлен позже правил: появляется ошибка Set blacklist doesn't exist, и часть цепочки не загружается. Сначала ipset, потом iptables или nftables.
- Правила применились, но их перезаписал Docker: демон сам управляет цепочками DOCKER и DOCKER-USER и пересоздаёт их при старте. Не блокируйте трафик контейнеров в цепочках, которые он контролирует. Если на хосте работает firewalld, часть фильтрации уходит в его зоны, разбор конфликтов и правил NAT есть в материале о маршрутизации и firewalld.
- Fail2ban не стартует: проверьте fail2ban-client -t и journalctl -u fail2ban. Частая причина - синтаксис в jail.local.
- База Fail2ban удалена или повреждена: баны не восстановятся. Верните файл /var/lib/fail2ban/fail2ban.sqlite3 из копии при остановленном сервисе либо забанить адреса заново.
Отдельно про тестовую перезагрузку по SSH. Держите вторую открытую сессию и отложенный reboot, чтобы успеть отменить его: shutdown -r +5, отмена - shutdown -c. На продакшн-сервере без консоли или KVM отложенный запуск остаётся самым безопасным способом проверить, что правила поднимаются.
Чек-лист: как гарантировать сохранение блокировок после перезагрузки
- Определите, где живут блокировки: iptables -S, nft list ruleset, ipset list, fail2ban-client status. Так вы поймёте, какие механизмы нужно закрепить.
- Для iptables сохраните состояние: netfilter-persistent save или iptables-save > /etc/iptables/rules.v4 плюс ip6tables-save > /etc/iptables/rules.v6.
- Для nftables соберите /etc/nftables.conf с заголовком #!/usr/sbin/nft -f и строкой flush ruleset, проверьте через nft -c -f и включите systemctl enable --now nftables.
- Для ipset сохраните наборы (ipset save) и убедитесь, что они восстанавливаются раньше правил фильтрации.
- Для Fail2ban включите автозапуск (systemctl enable fail2ban) и проверьте, что база в /var/lib/fail2ban доступна на запись.
- Если шагов несколько, соберите один systemd-юнит с нужным порядком, RemainAfterExit=yes и абсолютными путями.
- Проверьте на стенде: перезагрузите тестовый сервер и прогоните команды из раздела проверки. На прод-сервере используйте отложенный reboot и вторую SSH-сессию.
- Закройте права на файлы правил: chmod 600 /etc/iptables/rules.v4 /etc/ipset.conf /etc/nftables.conf, владелец root. Общий порядок усиления доступа и прав описан в руководстве по политике безопасности Linux-сервера.
Сразу после перезагрузки проверьте systemctl --failed: юнит персистентности в состоянии failed означает, что правила не загрузились, и дальше разбирать нужно его журнал, а не содержимое конфигов.
Источники
- iptables-restore(8) — Debian Manpages
- iptables-restore(8) — проект OpenNet
- netfilter-persistent(8) — Debian Manpages
- jail.conf(5) — Debian Manpages
- Manpage of nft — netfilter.org
- Nftables: руководство по работе с файрволом нового поколения — FirstVDS
- netfilter-persistent-plugin-ipset — GitHub
- fail2ban's database is too large — Server Fault