# fail2ban vs reaction

> fail2ban vs reaction (by ppom): streams and filters vs jails, YAML/JSONnet config, performance claims, AGPL license, and when reaction is the better fit.
>
> Source: https://fail2ban.dev/alternatives/reaction/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

**reaction** is an open-source daemon by ppom that watches the output of commands (usually `journalctl -f` or `tail -f`), counts repeated regex matches, and runs whatever commands you configure — typically a firewall ban and a delayed unban. **Pick reaction** if you want a lean, explicit config you write yourself and care about CPU use on log-heavy hosts. **Pick fail2ban** if you want 96 ready-made filters, distribution packages everywhere, a long track record and the largest pool of documentation and community answers.

reaction's author built it as a lighter replacement for fail2ban, and it shows: there is no default rule set to override, no `jail.conf`/`jail.local` layering, and every ban and unban is a command you can read in the config. The trade-off is that you write more yourself.

## fail2ban vs reaction at a glance

| Feature | fail2ban | reaction |
|---|---|---|
| Language | Python 3 | Rust (v2, a full rewrite of the earlier Go v1) |
| Detection source | Log files, systemd journal | Any command's stdout (streams), e.g. `journalctl -fu sshd.service` |
| Detection logic | Filter regexes with `<HOST>`, `maxretry` within `findtime` | Filter regexes with named patterns (`<ip>`), `retry` within `retryperiod` |
| Ready-made rules | 96 filters shipped upstream | Example filters in the wiki (SSH, Nginx, Traefik, Nextcloud, Proxmox, web crawlers…) |
| Response | Action files (nftables, iptables, firewalld, ufw, pf, Cloudflare, mail…) | Commands you write, plus plugins (nftables, ipset, HTTP ban page) |
| Ban expiry | `bantime`, `bantime.increment`, `recidive` | An action with `after:` (e.g. unban after 48h) |
| Scope | Single host | Single host |
| Config style | INI, layered `.conf` / `.local` files | One YAML, JSON or JSONnet file |
| IPv6 | Yes | Yes (`type: ip`, optional `ipv6mask` to ban a whole /64) |
| Persistence | SQLite database, bans restored at start | Embedded database in `/var/lib/reaction`, pending actions replayed at start |
| Packaging | Every major distribution | Release binaries and `.deb` for x86-64; some distributions and a NixOS module |
| License | GPL-2.0-or-later | AGPL-3.0 |
| Maintenance | Active, 1.1.1 (Aug 2026) | Active, v2.7.0 (24 Oct 2026) |

## How reaction works

A reaction configuration has four building blocks:

- **Streams**: commands whose output is read line by line, such as `journalctl -n0 -fu sshd.service`.
- **Patterns**: named regexes substituted into filters. The special `type: ip` matches IPv4 and IPv6 and supports `ignore` lists, `ignorecidr` ranges and masks (`ipv6mask: 64`).
- **Filters**: attached to a stream; a list of regexes plus `retry` and `retryperiod`. When the same pattern value (the IP) matches `retry` times within `retryperiod`, the filter triggers.
- **Actions**: commands run when a filter triggers. An action with `after:` runs later, which is how unbans are expressed; `oneshot: true` marks alert-style actions that shouldn't replay after a restart.

`start` and `stop` command lists set up and tear down firewall chains. A minimal SSH setup in YAML, adapted from the project's example configuration:

```yaml
patterns:
  ip:
    type: ip
    ignore: [ '127.0.0.1', '::1' ]
    ignorecidr: [ '192.168.1.0/24' ]

start:
  - [ 'iptables',  '-w', '-N', 'reaction' ]
  - [ 'iptables',  '-w', '-I', 'INPUT', '-j', 'reaction' ]
stop:
  - [ 'iptables',  '-w', '-D', 'INPUT', '-j', 'reaction' ]
  - [ 'iptables',  '-w', '-F', 'reaction' ]
  - [ 'iptables',  '-w', '-X', 'reaction' ]

streams:
  ssh:
    cmd: [ 'journalctl', '-n0', '-fu', 'ssh.service' ]
    filters:
      failedlogin:
        regex:
          - 'Failed password for .* from <ip>'
          - 'Invalid user .* from <ip>'
        retry: 3
        retryperiod: 6h
        actions:
          ban:
            cmd: [ 'iptables', '-w', '-A', 'reaction', '-s', '<ip>', '-j', 'DROP' ]
            ipv4only: true
          unban:
            cmd: [ 'iptables', '-w', '-D', 'reaction', '-s', '<ip>', '-j', 'DROP' ]
            after: 48h
            ipv4only: true
```

## How fail2ban compares

The equivalent fail2ban jail is shorter because the filter, the firewall action and the unban already exist:

```ini
[sshd]
enabled  = true
backend  = systemd
maxretry = 3
findtime = 6h
bantime  = 48h
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
```

Concept by concept:

| fail2ban | reaction |
|---|---|
| jail | filter (with an `after:` unban action) |
| `logpath` / `journalmatch` | stream `cmd` |
| `failregex` with `<HOST>` | `regex` with `<ip>` |
| `maxretry` / `findtime` | `retry` / `retryperiod` |
| `bantime` | `after:` on the unban action |
| `ignoreip` | pattern `ignore` / `ignorecidr` |
| `fail2ban-regex` | `reaction test-regex` / `test-filter` |
| `fail2ban-client status <jail>` | `reaction show` |

## Performance claims

reaction's README says it is much faster than fail2ban, and the author's blog reported lower CPU and memory use on their own server after switching. Those are the project's own measurements, not independent benchmarks, and we haven't measured it. What is structurally true: reaction is a compiled Rust binary with no interpreter, while fail2ban's cost grows with the number of log lines and regexes it evaluates. If fail2ban is using noticeable CPU on your server, try the fixes in [fail2ban high CPU](/troubleshooting/high-cpu/) first — journal matching and trimming unused jails often solve it.

## When reaction is the better choice

- You want every ban and unban spelled out in one file, with no defaults hidden in distribution overrides.
- You react to things that aren't logins — restarting a service on an error, alerting on a message — since reaction is a general "pattern in output → command" engine.
- Your host processes very large log volumes and CPU matters.
- You like JSONnet and want to generate configuration with variables and functions.

## When fail2ban is the better choice

- You protect many different services and want filters that already handle their log quirks.
- You want packages from your distribution's repositories and security updates through the normal channel.
- You need integrations that already exist as fail2ban actions: Cloudflare, AbuseIPDB, firewalld rich rules, email reports.
- You'd rather rely on a project with two decades of history and a wide user base. reaction's own README notes it has not had an external security audit yet.

## Running both, and migrating

Both can run on the same host as long as they don't manage the same service — give each its own firewall chain or set. To migrate a jail, translate each `failregex` into a reaction `regex` (replace `<HOST>` with `<ip>`, and drop fail2ban's date and prefix handling, which reaction doesn't do the same way), map `maxretry`/`findtime`/`bantime` as in the table above, then test with `reaction test-filter` against real log lines before disabling the fail2ban jail. When writing regexes, the [custom filter guide](/filters/custom/) and the [jail options reference](/config/jail-options/) help you understand what each fail2ban setting was doing.

## FAQ

### What is reaction?

reaction is a daemon that scans the output of commands such as `journalctl -f` for repeated patterns and runs configured actions, most often banning IPs in the firewall and unbanning them later. It is written in Rust, licensed under the AGPL-3.0, developed by ppom on Framagit, and configured in YAML, JSON or JSONnet.

### Is reaction better than fail2ban?

It is lighter and simpler to reason about, which suits people who want to write their own rules and care about CPU use. fail2ban has far more ready-made filters and actions, distribution packages everywhere and a much larger community. For most users protecting common services, fail2ban is less work.

### Is reaction stable enough for production?

The project considers its core mostly finished and released v2.7.0 in October 2026, after a series of 2.x releases funded in part by NLnet. Version 2.7.0 included breaking changes, so read the changelog before upgrading. The README notes that it hasn't had an external security audit.

### Can I convert fail2ban filters to reaction?

Yes, by hand. Copy each `failregex`, replace `<HOST>` with `<ip>`, remove anything that depends on fail2ban's prefix and date handling, and set `retry`, `retryperiod` and an `after:` unban to match `maxretry`, `findtime` and `bantime`. Test with `reaction test-filter` against real log lines.
