On this page
Configuration reference
Native TOML settings, durations, timed entries, sources, storage, enforcement and reload boundaries for fail2zig 0.4.8.
The default file is /etc/fail2zig/config.toml. Use the
worked example as a starting point and validate
before starting or reloading. This reference describes 0.4.8 native operation.
File format and permissions
The parser accepts a strict TOML subset: [global], [defaults] and
[jails.<name>]. Unknown keys/sections, duplicate keys, mixed array types and
invalid values are rejected. Use section headers, not dotted keys on the left.
Comments begin with #; strings must be quoted. Jail names use ASCII letters,
digits, hyphens or underscores.
Keep configuration administrator-owned and readable by the service. World-writable or non-root-group-writable configuration is refused. A non-root owner warns. The installer preserves existing operator configuration; check its permissions when changing the service group.
Duration strings
bantime, findtime, bantime_increment_max_bantime,
bantime_increment_jitter, bantime_increment_decay and ignoretime accept
integer seconds or quoted durations in both defaults and jail overrides. The
duration part of a banip or ignoreip entry uses the same terms as one word
inside the entry string, for example "198.51.100.0/24 1d".
[defaults]
bantime = "1h30m"
findtime = "10m"
bantime_increment_max_bantime = "1w"
bantime_increment_jitter = "30s"
bantime_increment_decay = "30d"| Unit | Seconds |
|---|---|
s | 1 |
m, mm, min | 60 |
h | 3,600 |
d | 86,400 |
w | 604,800 |
mo | 2,629,800 |
y | 31,557,600 |
Months and years are fixed intervals, not calendar arithmetic. Each term is a
nonnegative integer followed by a unit; terms can touch or have intervening
whitespace ("1h 30m"). Fractions, signed terms, arithmetic, unknown units and
overflow are rejected. Bare 24h is not valid TOML.
Finite bantime, findtime and the escalation cap must be positive. Jitter permits
zero and must not exceed the cap. 0 disables bantime_increment_decay and makes
ignoretime permanent. An entry’s duration must be finite, at least 1s and at
most 100 × 366 days. Only bantime accepts "permanent". On the command line,
ban --duration and ignore add --for accept seconds or this syntax; --timeout
and migration window arguments remain numeric.
[global]
Logging and administration
| Key | Type / default | Meaning |
|---|---|---|
log_level | String, "info" | debug, info, warn or err. Live reload supported. |
log_target | String, "stderr" | Stderr or an absolute log-file path. Changing the target requires restart; SIGUSR1 reopens it after rotation. |
log_format | String, "text" | "json" writes every daemon log line as one JSON object (ts, level, scope, message fields). Restart-only. |
socket_path | String, "/run/fail2zig/fail2zig.sock" | Local administration and monitoring socket. |
pid_file | String, "/run/fail2zig/fail2zig.pid" | Deprecated compatibility key; warns when explicitly set and is not written. Use systemd MainPID. |
The socket is mode 0660. The configured service/monitor group has read-only
access. Mutations require root or the daemon UID, verified with peer credentials.
Every mutation writes one audit line to the daemon log with the command, target,
caller UID and outcome. Use fail2zig --socket <path> status when overriding the
default socket.
State and resource limits
| Key | Type / default | Meaning |
|---|---|---|
state_file | Absolute path, "/var/lib/fail2zig/state.bin" | Native SQLite store for receipts, checkpoints, policy, retries, owners, effect intent, origins and confirmed history. |
memory_ceiling_mb | Integer, 64, minimum 16 | Configured memory budget; do not interpret it as a whole-process RSS cap. |
native_memory_ceiling_mb | Positive integer, 512 | Native admission budget in MiB. The plan for 65,536 live bans is about 337 MiB with two enforcing jails, 438 MiB with sixteen. |
native_fd_ceiling | Positive integer, 2048 | Native descriptor admission budget. |
history_retention | Nonnegative integer seconds, 86400 | Age policy for confirmed-history cleanup; required recovery anchors stay pinned. |
history_max_matches | Integer 0–1024, 10 | Confirmed-history match-retention policy. |
The database and its parent must belong to the daemon UID. The historical
.bin extension does not make it the legacy binary format. Native startup
refuses corrupt, incompatible or inaccessible required state instead of starting
empty. The file cannot grow past 1 GiB; a file already past that size is refused
at startup. When a commit finds the file full, ingestion pauses and retries while
rules already in the kernel stay enforced. The file never shrinks; rows removed by
retention free pages for reuse inside it, and status shows the size and page use.
A configuration that sets native_memory_ceiling_mb = 256 explicitly must raise
it or the daemon refuses with NativeMemoryAdmission. Storage failure pauses
affected ingestion and reports degraded health while retaining protection.
Preserve the database and WAL/SHM coherently; do not delete them to clear an error.
See upgrades and the
memory model.
HTTP and WebSocket
| Key | Type / default | Meaning |
|---|---|---|
metrics_enabled | Boolean, true | false disables both HTTP and WebSocket; IPC and protection remain available. |
metrics_bind | IP string, "127.0.0.1" | Listen address. Loopback is not authentication. |
metrics_port | Integer 1–65535, 9100 | Port remains validated even when disabled. Zero does not disable HTTP. |
websocket_max_clients | Integer 1–1024, 16 | Maximum simultaneous WebSocket clients. |
Endpoints are /metrics, /api/status, /api/bans, /api/health and /events.
Health reports readiness with HTTP 200/503. HTTP shares the privileged daemon
process. Keep external access controlled or disable it if unused. The per-jail
metric names are listed in the
administration reference.
Firewall
firewall is a global string, default "auto".
| Value | Selection behavior |
|---|---|
"auto" | Reuse the backend recorded for an existing installation; otherwise probe nftables, then ipset and iptables. |
"nftables" | Probe only nftables; direct netlink. |
"ipset" | Probe only ipset; needs both ipset and iptables tools. |
"iptables" | Probe only iptables. |
An explicit backend never silently switches to another. With existing state,
auto keeps the recorded backend across restarts, including when no bans are active.
An explicit backend or namespace selector that conflicts with the saved installation
is rejected rather than migrating ownership. Restore the recorded setting or use
auto; do not delete the database to switch backends. Jail enforce selects
enforcement policy, not a backend. If every enabled jail is log-only, enforcement
backend detection is skipped.
on_no_backend is "fail-closed" by default: unusable required enforcement causes
startup refusal. "log-only" explicitly permits degraded observe-only operation;
status reports the cause. It does not install protection.
firewall_namespace is an ownership anchor, not a request to enter another
namespace. Effects remain in the daemon’s current network namespace. Custom
selectors and unit overrides that move the daemon between namespaces are
unsupported; leave this setting at its built-in default.
DNS and timezone data
dns_server is an optional resolver IP; dns_port defaults to 53 and accepts
1–65535. Native hostname rules use bounded resolution and retain retry state;
a hostname is not an arbitrary command. timezone_root defaults to
"/usr/share/zoneinfo" for named timezone data. Leave these defaults unless the
chosen input contract requires an explicit deployment setting.
[defaults]
| Key | Type / default | Meaning |
|---|---|---|
bantime | Seconds/duration, 600, or "permanent" | Initial ban duration. |
findtime | Positive seconds/duration, 600 | Window for repeated failures. |
maxretry | Integer 1–128, 5 | Failures required within findtime. |
source | String, "auto" | auto, file, journald or internal. |
enforce | Boolean, true | true enforces through the selected global backend; false observes without enforcing. |
banaction | Deprecated string, "nftables" | Compatibility spelling; nftables, iptables, ipset mean enforce, and log-only means observe. |
ignoreip | String array, [] | IPs/CIDRs never banned, each optionally followed by a duration. The shipped example includes loopback ignores. |
banip | String array, [] | IPs/CIDRs banned while listed, permanent or timed ("198.51.100.0/24 1d"). Applied at start and on reload. |
ignoreself | Boolean, true | Adds the host’s own IPv4/IPv6 addresses, loopback included, to the effective ignore list with source self. |
ignoretime | Seconds/duration, unset (0, permanent) | Default life of an exclusion with no duration of its own, configured or ignore add without --for. |
Prefer enforce = true/false in new configurations. banaction remains accepted
for compatibility and emits a deprecation warning when explicitly set. Do not
combine the two in one section. Neither setting selects the firewall backend or
enables arbitrary shell actions.
Configured bans and exclusions
A banip entry is a live ban with origin config in that jail, permanent for a
permanent entry and ending at the stored deadline for a timed one. It takes over an
existing ban of the same address in the jail and replaces its expiry with its own.
Removing an entry and reloading lifts its ban at once. While a config ban is live,
fail2zig ban or unban of the address is refused with
banned by config: edit banip. A jail holds at most 1,024 effective banip
entries. Host names are not accepted.
An address in both the effective ignoreip and the effective banip of one jail,
as equal entries or one containing the other, is a configuration error: the daemon
refuses to start, and reload reports rejected naming the jail and the address.
ignoreself reads the host’s addresses from the kernel at start and at every
reload; an address assigned later counts from the next reload. self entries do
not count against the 128-entry ignore limit. A jail list for ignoreip or
banip replaces, rather than appends to, the [defaults] list.
ignoretime is unset or 0 for permanent. A jail value overrides [defaults],
and 0 in a jail restores permanent under a [defaults] value. A duration written
on the entry, or ignore add --for, wins. A non-zero value on a rule_files jail
is refused.
Timed entries
A banip or ignoreip entry written with a duration, and an ignoreip entry that
takes ignoretime, is timed. The period starts when the daemon first applies the
entry, at start or at a reload, and runs once: a reload or a restart does not
restart it. Changing the entry’s duration, or the ignoretime it takes, starts a
fresh period, as does removing the entry, reloading and adding it back. When the
period runs out the entry stays in the file and does nothing; list or
ignore list shows it as expired until it is removed from the file. A timed
ignoreip entry is refused on a jail with rule_files.
Escalation
| Key | Default | Meaning |
|---|---|---|
bantime_increment_enabled | false | Enable escalating durations. |
bantime_increment_multiplier | 1.0 | Positive base multiplier. |
bantime_increment_factor | 1.0 | Nonnegative growth factor; integer or decimal. |
bantime_increment_formula | "linear" | linear or exponential, not an arbitrary expression. |
bantime_increment_scope | "per-jail" | per-jail or overall recurrence scope. |
bantime_increment_max_bantime | 604800 | Positive seconds/duration cap. |
bantime_increment_jitter | 0 | Seconds/duration jitter bound, at most the cap. |
bantime_increment_decay | 2592000 | Escalation resets to the base bantime when the last ban ended longer ago than this. |
Linear uses bantime × multiplier × (1 + factor × n); exponential uses
bantime × multiplier × factor^n, where n is the prior ban count used by the
policy. For doubling with no jitter, set multiplier 1, factor 2 and formula
"exponential". A multiplier of 2 alone does not select doubling growth.
Decay is measured from the end of the last ban, not its start, so nothing decays
during a live ban and a permanent ban never decays. The reset is complete, never a
step down; 0 disables it. After a manual unban, failures count from zero again
while the escalation count is kept.
Escalation settings inherit field by field. A jail overrides only the keys it
explicitly sets; omitted keys retain [defaults] values.
[jails.<name>]
A jail declares a source, matcher and policy. Enabled jails must have admitted protection; unknown built-in filters without native rule files fail validation.
| Key | Default / contract |
|---|---|
enabled | true; disabled jails are not started. |
filter | Built-in service name, or the service name for a custom rule-file jail. Built-ins accept hyphen/underscore forms. |
logpath | Array of absolute file paths, default empty. Use file-source paths that the service can read. |
source | Inherits defaults. Prefer explicit source selection for reviewed configurations. |
maxretry, findtime, bantime, enforce | Override the corresponding default. banaction remains a deprecated compatibility input. |
ignoreip, banip | Each replaces, rather than appends to, the defaults list. Repeat loopback entries when overriding ignoreip. |
ignoreself, ignoretime | Override the [defaults] values for this jail. |
on_truncation | "refuse" or "restart"; file sources only, rejected in [defaults]. See log file lifecycle. |
bantime_increment_* | Field-by-field overrides as described above. |
on_truncation applies to every logpath of the jail and covers a file truncated
in place, a rotated-out file removed with unread bytes, and a log replaced by a
different file. refuse pauses the source until repair-source acknowledges the
loss; the daemon keeps running and other jails enforce. restart resumes and
records a continuity-loss event. The diagnostics are InvalidTruncationPolicy,
TruncationPolicyNotFileSource and TruncationPolicyNotAllowedInDefaults. A
change is restart-only. Neither value recovers records that were never read.
Source and time contracts
| Key | Contract |
|---|---|
timestamp | File inputs require an explicit format: iso8601, syslog, epoch_seconds, common_log or undated. Choose for the actual log format. |
timezone_offset_minutes | Fixed offset from UTC, -1439–1439, for the selected timestamp contract. |
timezone | Named timezone; cannot be combined with the fixed offset. |
timezone_ambiguity | reject, earlier or later; requires a named timezone. |
journal_executables | Optional exact origin profile. Built-in SSH jails discover trusted standard paths when omitted; custom journal rules require it. |
rule_files | Up to eight distinct absolute paths to bounded native JSON rules. |
ignore_file | Optional ignore-policy file for custom rule jails; its contents can be reloaded. |
[jails.sshd]
filter = "sshd"
source = "journald"
journal_executables = ["/usr/sbin/sshd", "/usr/lib/openssh/sshd-session"]auto resolves the configured source at startup. file reads declared paths;
journald requires journalctl and admitted origins; internal is the confirmed-ban
feed for recidive. Internal recidive does not use log paths or custom rule files.
A built-in SSH jail with no journal_executables setting discovers available
root-owned executables from a bounded catalog of standard SSH paths. Discovery
rejects unsafe path ownership or permissions. An explicit nonempty list remains an
exact override; an empty list is invalid, and custom journal rules require an explicit
profile.
A source is healthy only after its runtime checks pass; configuration validation
alone is not that check. A missing log file is reported as missing (FileNotFound)
by jails and is not a failure. See log watching
and the log file lifecycle.
For PortSentry v2.0.7, use filter = "portsentry", source = "file" and
timestamp = "iso8601" with the history file. Follow the
sensor and jail setup; this is not a generic syslog or
legacy PortSentry input. PortSentry recreates its history file at every start, so
choose on_truncation for that jail deliberately. File encoding replacement is
described under log watching.
Deprecated and migration-managed settings
backend is a deprecated source alias accepted in defaults/jails: systemd
maps to journald; auto, polling, pyinotify and gamin map to auto.
Using both source and backend in one section is an error. It never chooses
the firewall backend.
native_ingestion defaults to true; disabling native operation is not a supported
deployment route. compatibility_manifest and compatibility_pending are
migration-managed admission metadata. Do not clear blockers by hand to force an
unsupported imported jail into service.
Validation and reload
sudo fail2zig --validate-config --config /etc/fail2zig/config.tomlDiagnostics identify file, line, column, section and key when available. Unreadable
files and permission failures can be positionless. Validation summarizes configured
sources and prints on_truncation=<value> for each file jail; an auto source is
resolved when the daemon starts. Inspect the actual source and readiness through
fail2zig jails and fail2zig status.
Some settings apply through fail2zig reload; others return restart_required.
Adding a jail to the file and removing one are live changes. See the
reload table. Neither successful
syntax validation nor a reload request establishes installed firewall protection.
The tagged parser, shipped example and fail2zig.toml(5) define the release’s accepted configuration.