fail2ban-rs 1.0.0

A pure-Rust fail2ban replacement. Single static binary, fast two-phase matching, nftables/iptables firewall backends.
Documentation

A ground-up Rust rewrite of fail2ban5x faster matching · 6.6x faster startup · single binary · zero database · zero locks

Used in production at tell.rs to protect SSH and application endpoints.

fail2ban is a 20-year-old Python codebase that works, but requires a Python runtime on every production server, serializes all firewall operations behind a global thread lock, and executes shell commands via subprocess.Popen(shell=True).

fail2ban-rs eliminates all of that:

  • Single 3.2 MB binary — no Python, no runtime, no interpreter startup overhead
  • ~6 MB RSS in production — constant memory regardless of log volume
  • Zero locks — three async tasks connected by channels, single-owner state (Python fail2ban uses 9+ thread locks)
  • 5x faster per-line matching — Aho-Corasick pre-filter + AC-guided regex selection
  • No shell execution — nftables/iptables backends exec directly via argv, no shell=True (script backend uses sh -c but substitutes only validated IpAddr values)
  • 6.6x faster startup — 3.7ms vs 25.8ms (measured with hyperfine, 50 runs)
  • 67% less code — 4,200 lines of Rust vs 12,500 lines of Python
  • Constant-size state — flat binary snapshot of active bans only. No SQLite database growing on disk for years
  • ~1 MB at 10K active bans — ring buffers store 5 timestamps per IP, not matched log lines

Everything else you'd expect: nftables/iptables/script backends, ban time escalation, config overlays, hot reload via SIGHUP, 19 built-in filters, systemd journal support.

Install

Requires Linux and systemd. Installs the binary, systemd service, and default config.

curl -sSfL https://raw.githubusercontent.com/aejimmi/fail2ban-rs/main/scripts/install.sh | bash
nano /etc/fail2ban-rs/config.toml     # edit config
systemctl enable fail2ban-rs          # start on boot
systemctl start fail2ban-rs           # start
fail2ban-rs status                    # check status
journalctl -u fail2ban-rs -f          # logs

Configuration

See config/default.toml for all options. Minimal jail:

[jail.sshd]
enabled = true
log_path = "/var/log/auth.log"
date_format = "syslog"
filter = [
    'sshd\[\d+\]: Failed password for .* from <HOST>',
    'sshd\[\d+\]: Invalid user .* from <HOST>',
]
port = ["22"]
protocol = "tcp"
max_retry = 5
find_time = "10m"
ban_time = "1h"
backend = "nftables"

# Ban time escalation for repeat offenders
bantime_increment = true
bantime_multipliers = [1, 2, 4, 8, 16, 32, 64]
bantime_maxtime = "1w"

# IPs/CIDRs to never ban
ignoreip = ["127.0.0.1/8", "::1/128"]
ignoreself = true

Durations accept s, m, h, d, w suffixes (e.g. "10m", "1h", "7d"). Raw seconds also work.

Firewall backends

nftables (default): Creates table inet fail2ban-rs, chain, and per-jail sets. Teardown on shutdown.

iptables: Per-jail chains with multiport matching. Manages both iptables and ip6tables.

script: Custom commands with <IP> and <JAIL> placeholders:

[jail.custom.backend.script]
ban_cmd = "/usr/local/bin/ban.sh <IP> <JAIL>"
unban_cmd = "/usr/local/bin/unban.sh <IP> <JAIL>"

Config overlays

Additional .toml files in config.d/ next to your main config are merged alphabetically.

Built-in filters

fail2ban-rs gen-config <name> generates a jail config for any of these:

sshd nginx-auth nginx-botsearch postfix dovecot vsftpd asterisk mysqld apache-auth apache-botsearch vaultwarden bitwarden proxmox gitlab grafana haproxy drupal traefik openvpn

CLI

fail2ban-rs status                              # show all jails and bans
fail2ban-rs ban 1.2.3.4 sshd                    # manually ban an IP
fail2ban-rs unban 1.2.3.4 sshd                  # manually unban
fail2ban-rs regex --pattern '...' --line '...'  # test a pattern
fail2ban-rs gen-config sshd                     # generate jail config
systemctl reload fail2ban-rs                    # hot reload (SIGHUP)

Testing

Test patterns and dry-run against real logs — without touching any firewall.

# verify a pattern extracts the right IP from a log line
fail2ban-rs regex --pattern 'sshd\[\d+\]: Failed password for .* from <HOST>' \
  --line 'sshd[1234]: Failed password for root from 10.0.0.1 port 22 ssh2'

# dry-run against a real log file — shows which IPs would be banned
fail2ban-rs dry-run /var/log/auth.log --jail sshd

Performance

Per-line matching pipeline benchmarks (MacBook M4 Pro, criterion), comparing against Python fail2ban's equivalent regex engine. Line mix based on openssh_2k.log from logpai/loghub (~30% hits, ~70% near-misses):

Stage Rust Python Speedup
Full pipeline (openssh_2k mix) ~147 ns/line ~740 ns/line 5x
Pattern match — hit 291-353 ns 457-730 ns 1.6-2.1x
Pattern match — miss (AC rejects) 20-56 ns 342-574 ns 6-29x
Date parse (ISO 8601) 7.6 ns 165 ns 22x

Run benchmarks yourself:

cargo bench --bench matching                 # Rust (criterion)
python3 benches/bench_matching_fail2ban.py   # Python (timeit)

Building from source

cargo build --release
cargo test

Migration from fail2ban

fail2ban fail2ban-rs
/etc/fail2ban/jail.conf /etc/fail2ban-rs/config.toml
failregex = ... filter = ['...']
maxretry = 5 max_retry = 5
findtime = 10m find_time = "10m"
bantime = 1h ban_time = "1h"
bantime.increment = true bantime_increment = true
bantime.multipliers = 1 2 4 8 bantime_multipliers = [1, 2, 4, 8]
action = iptables[...] backend = "iptables"
ignoreip = 127.0.0.1/8 ignoreip = ["127.0.0.1/8"]
fail2ban-client status fail2ban-rs status
fail2ban-client set sshd banip 1.2.3.4 fail2ban-rs ban 1.2.3.4 sshd

Roadmap

  • cargo install fail2ban-rs / apt install — additional distribution channels
  • Plugin-style filters and actions — Cloudflare, AbuseIPDB, Slack integrations as config files
  • Threat feed blocking — import IP blocklists and block known attackers proactively
  • Cross-server ban sharing — lightweight registry so one node's detection blocks across the cluster
  • Repeat offender escalation — IPs banned across multiple jails auto-escalate to longer bans
  • Operational CLI — unban --all, live dashboard, check if a specific IP is banned

Sponsoring helps prioritize these.

License

MIT