On this page
Migrating from fail2ban
Inspect supported configuration, transfer schema-4 SQLite state and plan continuity, cutover and rollback with fail2zig 0.4.0.
fail2ban configuration inspection and state migration are separate operations. Review supported protection before switching: a matching filter name does not prove equivalent detection, and unsupported enabled protection blocks activation. For an existing fail2zig installation, use Upgrading to 0.4.0.
Before you start
Use the 0.4.0 executable and retain a working source installation for rollback. Selected live qualification used Debian 13 x86_64; other release architectures have build/static/QEMU smoke evidence. Ubuntu and non-x86 hardware enforcement remain untested. A kernel version alone does not establish support.
Journal input needs journald and journalctl. nftables uses direct netlink; iptables/ipset require their host tools. Native destination state and private staging must belong to the service UID. Read the complete workflow before stopping protection, including the temporary privileges needed for offline rollback.
What transfers
The supported schema-4 SQLite workflow can transfer admitted protection owners, original deadlines and confirmed history. It inspects scope and continuity, records blockers and journals cutover/rollback. It does not point the native daemon at the foreign live database.
What does not transfer
- Arbitrary fail2ban regexes, filter definitions, Python extensions, mail actions and shell action templates are not executed by fail2zig. Use reviewed built-ins or bounded native JSON rules.
- In-memory partial matches, in-window failure counters and multiline correlation state are absent from fail2ban’s database and cannot be transferred.
- fail2ban has no saved journal cursor. Journal migration needs an explicit reset/replay disposition; it cannot claim lossless log-position continuity.
- fail2ban’s socket protocol and runtime mutation API are not supported. Use native administration.
- Arbitrary bantime formulas are not evaluated. Review native linear/exponential multiplier, factor, cap, scope and jitter semantics rather than assuming equivalence.
Inspect and project configuration
sudo fail2zig migrate inspect --source-dir /etc/fail2ban
sudo fail2zig --import-config /etc/fail2ban --import-output /tmp/fail2zig-review.toml
sudo fail2zig --validate-config --config /tmp/fail2zig-review.tomlThe separate review path avoids replacing a working configuration. Projection alone transfers no state and proves no live protection. Review all warnings, disabled or blocked jails, log paths, source origins, timestamp contracts, ignore policy and action scope. Unsupported protection needs an explicit replacement or disposition before activation, not a silent loss of coverage.
global.firewall chooses the daemon backend. Jail banaction selects enforcement
or log-only policy, not an independent backend. Use log-only operation with separate
state to compare representative inputs while fail2ban retains protection; do not
assume identical decisions from a short observation window.
Quoted native durations such as "1h" are valid in the four documented fields.
See configuration for exact keys and inheritance.
Migration workflow
This workflow accepts supported fail2ban schema-4 SQLite input. It does not convert fail2zig v0.3.0 binary state. For an existing native database’s service-account transition, follow upgrading fail2zig state.
The offline preparation commands never touch the running fail2ban service, its database or the firewall:
fail2zig migrate inspect --source-dir /etc/fail2ban [--output json|table]
fail2zig migrate snapshot --source-db /var/lib/fail2ban/fail2ban.sqlite3 --staging-dir <dir>
fail2zig migrate plan --source-dir /etc/fail2ban --source-db <db> --staging-dir <dir> \
--out plan.json [--continuity lossless|reset-replay] [--replay-window <s>] \
[--runtime-socket <path>]
fail2zig migrate validate --plan plan.json [--source-dir <dir>] [--snapshot <file>]
--staging-dir must be a private (0700), non-volatile directory; reset-replay continuity
needs --replay-window, otherwise the plan records a replay-window-missing blocker.
--runtime-socket records the source service’s control socket for a later rollback
(default /var/run/fail2ban/fail2ban.sock).
Run preparation and native destination operations as the same UID that will own the native
daemon store. A root-created destination database is not reopenable by the fail2zig service
until a safe offline ownership transition. With the account installed, prepare a private,
persistent staging directory owned by it, and invoke commands with the required capabilities:
The source file and its ancestors must also grant the service UID ordinary read/search
permission: the source admission check uses the real UID, so capabilities alone do not satisfy
it. If the source already has a restricted reader group, grant that group only to the offline
command with -p 'SupplementaryGroups=<existing-source-reader-group>'. Do not add that membership
to the shipped daemon or make the source writable/world-readable to bypass the check.
sudo install -d -o fail2zig -g fail2zig -m 0700 /var/lib/fail2zig-migration
sudo systemd-run --wait --pipe --uid=fail2zig --gid=fail2zig \
-p 'AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_DAC_READ_SEARCH' \
/usr/local/bin/fail2zig migrate snapshot \
--source-db /var/lib/fail2ban/fail2ban.sqlite3 \
--staging-dir /var/lib/fail2zig-migrationUse this invocation prefix for plan, validate, native cutover and status as well,
with absolute paths. Substitute the configured service group if it differs. Staging must
belong to the invoking UID, and the native destination parent/database must belong to the
daemon UID; configuration and executable files remain administrator-owned. The capability
grant does not make an inaccessible source path writable or bypass systemd mount restrictions.
Keep the destination daemon stopped while creating/staging native state. The commands below
show the migration arguments; apply the account/capability prefix above for destination access.
The cutover is journaled and resumable:
systemctl stop fail2ban
fail2zig migrate cutover --plan plan.json --state-file /var/lib/fail2zig/state.bin \
--staging-dir <dir> --backend nftables|ipset|iptables [--socket <path>]
fail2zig migrate status --plan plan.json --state-file <db> --staging-dir <dir> --run-id <hex>
Staging happens offline with the daemon stopped; activation and kernel verification run
inside the daemon over the socket. When the socket is unreachable the command exits 3 and
prints the exact --run-id command to resume; a rerun with --run-id classifies the journal
and the store instead of replaying. Exit mapping: before any mutation every failure is 1;
after a mutation, incomplete protection is 4 (partial) and an unestablished outcome is 5
(uncertain); pending is 3.
The disclosed protection gap is the interval between stopping fail2ban and the daemon’s activation. Start the destination service explicitly after offline staging, then use the printed resume command against its socket to complete activation. The installer does not perform this handoff.
Rollback
Source restoration must write beside the original fail2ban database and restore its original UID, GID and mode. The ordinary service capabilities are insufficient. Stop both source and destination writers, preserve their current coherent databases, and run the offline restore as the native service UID with a temporary capability grant. For a root-owned source:
# RUN_ID is the recorded migration run ID. Use the original backend and paths.
sudo systemctl stop fail2ban fail2zig
sudo systemd-run --wait --pipe --uid=fail2zig --gid=fail2zig \
-p 'AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_CHOWN CAP_FOWNER' \
-p 'CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_CHOWN CAP_FOWNER' \
/usr/local/bin/fail2zig migrate rollback \
--plan /var/lib/fail2zig-migration/plan.json \
--state-file /var/lib/fail2zig/state.bin \
--staging-dir /var/lib/fail2zig-migration --backend nftables --run-id "$RUN_ID"Substitute the configured group and, if needed, the transient source-reader group described
above. These additional capabilities belong only to this operator-launched offline command;
do not add them to fail2zig.service. The original source parent ownership is retained, and
the restored database receives the source’s original ownership and mode. Read-only mounts or
other filesystem restrictions still require operator repair.
After the restore reports its next step, start fail2ban and verify source protection, then
start the destination daemon so the second --source-verified operation can release its
owners through IPC. That second operation uses the ordinary migration invocation prefix;
the extra restore capabilities are unnecessary. Keep the source serving until rollback
completion is confirmed. The general arguments are:
fail2zig migrate rollback --plan plan.json --state-file <db> --staging-dir <dir> \
--backend nftables|ipset|iptables --run-id <hex> [--socket <path>]
fail2zig migrate rollback ... --run-id <hex> --source-verified
Only an activated run needs rollback; a staged run is discarded by not activating it, and
a run whose cutover step is still open must first be resumed with migrate cutover --run-id.
The rollback is journaled under the same run in two phases:
- Restore the source. The recovery point
recovery-point-<run16>.sqlite3in the staging directory is validated against the plan’s snapshot, and the live source database is fingerprinted through the SQLite backup API (WAL-aware). A recovery-point or source fingerprint mismatch finishes the step asrollback_failedwith nothing written (exit 4). Environment failures (unreadable recovery point or source, no space, a failed rename after which the original is put back) areoperational_failure, exit 1, and the run stays resumable. Otherwise the post-cutover deltas are carried back: native bans intobans/bips, released or expired staged owners removed frombips, and scopes with protocol or port restrictions reported asunsupported. The derived database is written beside the source, fsync’d and given the original’s owner and mode; the original moves to<source>.pre-rollback-<run16>together with its-wal/-shmsiblings (nothing is deleted); the derived file is renamed into place and the directory is synced; a durable markerrestored-<run16>in the staging directory records the completed swap. The command then exits 3 and prints the next command. - Release the destination. Requires
--source-verified, the source control socket recorded in the plan, and a runningfail2ban-serverprocess (a stale socket is refused). The daemon accepts the release only while the rollback step is open; native owners stay.appliedsets the run state torolled_backand exits 0. A partial (4), uncertain (5) or refused (1) release leaves the step open for a rerun; an unreachable daemon or a rejected request exits 3.
A rerun with --run-id classifies by the marker and the files: marker present, release only;
source missing with the derived file present, the swap is completed; source missing with only
the kept copy, it is moved back and the restore repeats; source unchanged, the restore repeats;
source changed with no marker, rollback_failed. Not lab-exercised: bans that expire during
the outage window, and a rename failure after the original has been moved aside.
Continuity and replay
“Lossless” describes admitted file positions, not every piece of fail2ban state. A file resumes only when its recorded position and identity checks admit it. Missing records, replacement, truncation or unreadability can block lossless activation. The native daemon verifies the resulting device/inode/offset checkpoint again before reading.
Journal sources have no transferable cursor. With reset/replay, records inside the declared replay window may be counted again, including failures fail2ban already counted. Imported active bans are reconciled and exact duplicate guards apply within one native source, but this does not make the transition lossless. The planner requires a replay window for reset/replay plans.
Review the versioned continuity contract for per-file decisions and duplicate boundaries.
Verify protection
After activation or rollback, check the running version, source health, storage and installed owners. Inspect the selected backend and exercise a controlled external test path; a successful command or open HTTP connection is insufficient.
sudo fail2zig version
sudo fail2zig status
sudo fail2zig jails
sudo fail2zig list
sudo fail2zig history
sudo nft list table inet fail2zigThe final command applies to nftables; use the appropriate backend inspection for iptables/ipset. Owned set/rule names and scope should be read from the actual backend rather than assumed from an older release. Keep default loopback ignores; 0.4.0 has a known loopback SSH cleanup limitation, so loopback is not the test path.
This guide follows the 0.4.0 command migration contract. Rehearse the selected source, scope and failure/rollback cases before a production transition. Qualification does not cover every installation or every interruption.