On this page
Filters and native rules
Built-in service matchers, PortSentry history input and bounded native JSON rules for fail2zig 0.4.8.
fail2zig ships 16 compiled built-in filter names (15 external-service filters plus
internal recidive) and supports bounded custom
JSON rules through rule_files. Built-in names accept hyphen/underscore aliases.
A service name does not establish equivalence with a fail2ban filter of the same name.
The examples below are matcher-oriented starting points. Verify your actual log
format and time contract with fail2zig rule-test; file jails need an explicit
timestamp. The file examples use undated deliberately, so select a dated contract
when your input provides appropriate event timestamps. Validation does not prove
live source readiness. Keep default loopback ignores in all enforcing examples.
SSH
sshd
What it matches: Supported OpenSSH failure-message forms, including password, invalid-user, PAM, protocol and authentication-limit messages. This is a format contract, not qualification of every OpenSSH version or distribution.
Log file:
| Distribution | Path |
|---|---|
| Debian, Ubuntu | /var/log/auth.log |
| RHEL, Fedora, CentOS, Rocky | /var/log/secure |
| systemd journal | source = "journald" (auto-selected when no logpath exists) |
Example matching lines (taken from filter test suite):
Failed password for root from 192.168.1.100 port 43210 ssh2
Failed password for invalid user admin from 10.0.0.1 port 22 ssh2
Invalid user testuser from 203.0.113.50 port 54321
Connection closed by authenticating user root 45.227.253.98 port 11111 [preauth]
Disconnected from authenticating user root 45.227.253.98 port 11111 [preauth]
error: PAM: Authentication failure for root from 1.2.3.4
Received disconnect from 192.0.2.10 port 22:11: Bye Bye [preauth]
Bad protocol version identification 'GET / HTTP/1.0' from 1.2.3.4 port 55678
maximum authentication attempts exceeded for root from 198.51.100.1 port 22 ssh2Recommended jail settings:
[jails.sshd]
enabled = true
filter = "sshd"
source = "journald"
journal_executables = ["/usr/sbin/sshd", "/usr/lib/openssh/sshd-session"]
maxretry = 3 # SSH is high-value; tighten the threshold
bantime = 3600 # 1 hour first offense; pair with bantime_incrementWhen to tune: Lower maxretry to 2 on servers that see constant brute-force
volume. Enable bantime increment (see [defaults]) — SSH attackers typically
probe the same IP multiple times over days.
Web
nginx-http-auth
What it matches: nginx basic authentication failures from the nginx error log. Covers: no credentials provided, user not found in htpasswd file, and password mismatch.
Log file: /var/log/nginx/error.log
Example matching lines:
2026/04/21 12:00:00 [error] 1234#0: *1 no user/password was provided for basic authentication, client: 1.2.3.4, server: example.com
[error] 1#0: *2 user "admin": was not found in "/etc/nginx/.htpasswd", client: 203.0.113.50, server: _
[error] user "foo": password mismatch, client: 9.9.9.9, server: appRecommended jail settings:
[jails.nginx-http-auth]
enabled = true
filter = "nginx-http-auth"
source = "file"
timestamp = "undated"
logpath = ["/var/log/nginx/error.log"]
maxretry = 5
bantime = 600nginx-limit-req
What it matches: nginx request-rate-limit violations from limit_req_zone
and connection-rate violations from limit_conn_zone. Useful for banning clients
that repeatedly trigger rate limits, indicating automated abuse rather than
accidental misconfiguration.
Log file: /var/log/nginx/error.log
Example matching lines:
[error] 1#0: *1 limiting requests, excess: 0.500 by zone "one", client: 1.2.3.4, server: _
[error] limiting requests, excess: 10 by zone "req_zone", client: 203.0.113.1
[error] 1#0: *1 limiting connections by zone "addr", client: 5.6.7.8Recommended jail settings:
[jails.nginx-limit-req]
enabled = true
filter = "nginx-limit-req"
source = "file"
timestamp = "undated"
logpath = ["/var/log/nginx/error.log"]
maxretry = 10 # rate-limit bursts are common; tune to your traffic baseline
findtime = 60
bantime = 300When to tune: If legitimate users behind NAT trigger rate limits,
increase maxretry or add their shared egress IP to ignoreip. Reduce
findtime to 60 seconds if you want to catch rapid bursts rather than
sustained traffic.
nginx-botsearch
What it matches: Bots probing well-known attack paths in the nginx access
log. Covers: WordPress wp-login.php, xmlrpc.php, phpMyAdmin, .env files,
and .git directories.
Log file: /var/log/nginx/access.log
Example matching lines:
1.2.3.4 - - [21/Apr/2026:12:00:00 +0000] "GET /wp-login.php HTTP/1.1" 404 162
203.0.113.50 - - [21/Apr/2026:12:00:00 +0000] "POST /wordpress/wp-login.php HTTP/1.1" 404 500
1.2.3.4 - - [21/Apr/2026] "POST /xmlrpc.php HTTP/1.1" 404 162
9.9.9.9 - - [21/Apr/2026] "GET /phpmyadmin/ HTTP/1.1" 404 0
5.6.7.8 - - [21/Apr/2026] "GET /.env HTTP/1.1" 404 0
1.2.3.4 - - [21/Apr/2026] "GET /.git/config HTTP/1.1" 404 0Recommended jail settings:
[jails.nginx-botsearch]
enabled = true
filter = "nginx-botsearch"
source = "file"
timestamp = "undated"
logpath = ["/var/log/nginx/access.log"]
maxretry = 2 # even one probe is suspicious; two is a pattern
findtime = 3600 # wide window — bots may space probes out
bantime = 86400 # 1 day for confirmed scannerapache-auth
What it matches: Apache httpd basic authentication failures from the Apache
error log. Covers: authentication failure, user not found, and password mismatch
messages from mod_auth_basic and related modules.
Log file: /var/log/apache2/error.log (Debian/Ubuntu); /var/log/httpd/error_log (RHEL)
Example matching lines:
[Tue Apr 21 12:00:00.000 2026] [auth_basic:error] [pid 1:tid 2] [client 1.2.3.4:12345] user admin: authentication failure
[error] [client 203.0.113.50:80] user foo: authentication failure for "/admin"
[error] [client 1.2.3.4:22] user doesntexist not found: /admin
[error] [client 1.2.3.4:22] AH01617: user admin: password mismatch: /secretRecommended jail settings:
[jails.apache-auth]
enabled = true
filter = "apache-auth"
source = "file"
timestamp = "undated"
logpath = [
"/var/log/apache2/error.log",
"/var/log/httpd/error_log",
]
maxretry = 5
bantime = 600apache-badbots
What it matches: Well-known abusive user-agents in the Apache access log
(AhrefsBot, SemrushBot, MJ12bot, DotBot) and access-log 404s for WordPress
wp-login.php and xmlrpc.php.
Log file: /var/log/apache2/access.log (Debian/Ubuntu); /var/log/httpd/access_log (RHEL)
Example matching lines:
1.2.3.4 - - [21/Apr/2026:12:00:00 +0000] "GET / HTTP/1.1" 200 1024 "-" "Mozilla/5.0 (compatible; AhrefsBot/7.0)"
5.6.7.8 - - [21/Apr/2026:12:00:00 +0000] "GET / HTTP/1.1" 200 512 "-" "SemrushBot/7~bl"
1.2.3.4 - - [21/Apr/2026] "GET /wp-login.php HTTP/1.1" 404 162 "-" "curl/7.0"Recommended jail settings:
[jails.apache-badbots]
enabled = true
filter = "apache-badbots"
source = "file"
timestamp = "undated"
logpath = [
"/var/log/apache2/access.log",
"/var/log/httpd/access_log",
]
maxretry = 2
bantime = 86400apache-overflows
What it matches: Malformed or oversized HTTP requests from the Apache error log. Covers: invalid URI, URI too long (414), and error reading headers — patterns associated with buffer overflow probes and scanner tooling.
Log file: /var/log/apache2/error.log (Debian/Ubuntu); /var/log/httpd/error_log (RHEL)
Example matching lines:
[error] [client 1.2.3.4:22] Invalid URI in request GET /<garbage> HTTP/1.0
[error] [client 203.0.113.1:80] request failed: URI too long (longer than 8190)
[error] [client 10.0.0.5:55678] request failed: error reading the headersRecommended jail settings:
[jails.apache-overflows]
enabled = true
filter = "apache-overflows"
source = "file"
timestamp = "undated"
logpath = [
"/var/log/apache2/error.log",
"/var/log/httpd/error_log",
]
maxretry = 2
bantime = 3600postfix
What it matches: Postfix SMTP-level abuse. Covers: RCPT address rejection
(NOQUEUE: reject), SASL authentication failures, and lost-connection-after-AUTH
patterns indicating credential stuffing.
Log file: /var/log/mail.log (Debian/Ubuntu); /var/log/maillog (RHEL)
Example matching lines:
Apr 21 12:00:00 mail postfix/smtpd[1234]: NOQUEUE: reject: RCPT from unknown[1.2.3.4]: 554 5.7.1 Relay access denied
Apr 21 12:00:01 mail postfix/smtpd[1234]: NOQUEUE: reject: RCPT from host.example.com[203.0.113.50]: 450 4.1.2 Domain not found
Apr 21 12:00:00 mail postfix/smtpd[1234]: warning: unknown[1.2.3.4]: SASL LOGIN authentication failed: authentication failure
Apr 21 12:00:00 mail postfix/smtpd[1234]: lost connection after AUTH from mail.example.com[10.0.0.1]Recommended jail settings:
[jails.postfix]
enabled = true
filter = "postfix"
source = "file"
timestamp = "undated"
logpath = [
"/var/log/mail.log",
"/var/log/maillog",
]
maxretry = 5
findtime = 600
bantime = 1800When to tune: Mail servers legitimately see high reject rates from misconfigured
senders. Consider raising maxretry to 10 if you’re seeing legitimate MTA IPs
getting banned. Add well-known transactional mail provider IP ranges to
ignoreip if needed.
dovecot
What it matches: Dovecot IMAP and POP3 authentication failures. Covers:
imap-login: Disconnected (auth failed), pop3-login: Disconnected (auth failed),
and PAM authentication failures. A disconnect without authentication attempts is
not one of the built-in failure patterns.
Log file: /var/log/mail.log (Dovecot typically writes to the same log as Postfix)
Example matching lines:
Apr 21 12:00:00 mail dovecot: imap-login: Disconnected (auth failed, 1 attempts in 2 secs): user=<foo>, method=PLAIN, rip=1.2.3.4, lip=10.0.0.1
Apr 21 12:00:00 mail dovecot: pop3-login: Disconnected (auth failed, 1 attempts): user=<bar>, rip=203.0.113.50
Apr 21 12:00:00 mail dovecot: auth-worker(1234): pam(username,1.2.3.4): pam_authenticate() failedRecommended jail settings:
[jails.dovecot]
enabled = true
filter = "dovecot"
source = "file"
timestamp = "undated"
logpath = ["/var/log/mail.log"]
maxretry = 5
bantime = 1800courier
What it matches: Courier authentication failures, including the
LOGIN FAILED form used by current Courier builds and the older imaplogin: FAILED
form from legacy versions.
Log file: Courier writes to syslog; path depends on your syslog configuration.
Common paths: /var/log/mail.log, /var/log/courier/errors.
Example matching lines:
Apr 21 12:00:00 mail authdaemond: LOGIN FAILED, user=foo, ip=[::ffff:1.2.3.4]
Apr 21 12:00:00 mail authdaemond: imaplogin: FAILED, ip=[::ffff:203.0.113.50]Recommended jail settings:
[jails.courier]
enabled = true
filter = "courier"
source = "file"
timestamp = "undated"
logpath = ["/var/log/mail.log"]
maxretry = 5
bantime = 1800Note: Courier wraps IPv4 addresses in IPv6 notation (::ffff:1.2.3.4). fail2zig
canonicalizes these to their IPv4 form at the parse boundary, so they will be
tracked and banned as IPv4 addresses and will match IPv4 CIDR entries in ignoreip.
DNS
named-refused
What it matches: BIND (named) queries that were denied or refused. Two
patterns cover the modern denied log message and the legacy REFUSED message
from older BIND versions.
Log file: /var/log/named/default or /var/log/syslog depending on BIND configuration.
Example matching lines:
21-Apr-2026 12:00:00.000 client @0xabc 1.2.3.4#53 (example.com): query (cache) 'example.com/A/IN' denied
21-Apr-2026 12:00:00.000 client 203.0.113.50#53: REFUSEDRecommended jail settings:
[jails.named-refused]
enabled = true
filter = "named-refused"
source = "file"
timestamp = "undated"
logpath = ["/var/log/named/default", "/var/log/syslog"]
maxretry = 10 # DNS resolvers have high query rates; tune carefully
findtime = 60
bantime = 600When to tune: Misconfigured resolvers and CDN nodes may legitimately query your DNS server. Consider monitoring ban events before committing to low thresholds.
FTP
vsftpd
What it matches: vsftpd login failures. Two patterns: the current
FAIL LOGIN: Client "<IP>" format and the older
Authentication failed for user x from <IP> format.
Log file: /var/log/vsftpd.log
Example matching lines:
[pid 1234] [user] FAIL LOGIN: Client "1.2.3.4"
[pid 5678] Authentication failed for nobody from 203.0.113.50Recommended jail settings:
[jails.vsftpd]
enabled = true
filter = "vsftpd"
source = "file"
timestamp = "undated"
logpath = ["/var/log/vsftpd.log"]
maxretry = 5
bantime = 3600proftpd
What it matches: ProFTPD login failures. Three patterns: no such user,
Login failed: Incorrect password, and the SECURITY VIOLATION message from
ProFTPD’s tilde-path protection.
Log file: /var/log/proftpd/proftpd.log or /var/log/auth.log depending on
ProFTPD’s SystemLog directive.
Example matching lines:
mod_auth.c(..): no such user 'admin' from 1.2.3.4
mod_auth.c(..): USER admin (Login failed): Incorrect password from 203.0.113.50
SECURITY VIOLATION: root login attempted from 10.0.0.5Recommended jail settings:
[jails.proftpd]
enabled = true
filter = "proftpd"
source = "file"
timestamp = "undated"
logpath = ["/var/log/proftpd/proftpd.log"]
maxretry = 5
bantime = 3600Database
mysqld-auth
What it matches: MySQL and MariaDB access-denied messages. Two patterns cover
the MySQL 5.x format (Access denied for user 'x'@'1.2.3.4') and the alternative
form where the IP appears after from '.
Log file: /var/log/mysql/error.log (Debian/Ubuntu);
/var/log/mysqld.log (RHEL); MariaDB: /var/log/mariadb/mariadb.log
Example matching lines:
[Warning] Access denied for user 'root'@'1.2.3.4' (using password: YES)
2026-04-21T12:00:00.000000Z 0 [Warning] Access denied for user 'admin'@'203.0.113.50'Recommended jail settings:
[jails.mysqld-auth]
enabled = true
filter = "mysqld-auth"
source = "file"
timestamp = "undated"
logpath = [
"/var/log/mysql/error.log",
"/var/log/mysqld.log",
"/var/log/mariadb/mariadb.log",
]
maxretry = 5
bantime = 3600When to tune: MySQL access-denied messages are also emitted for
application-level credential errors (misconfigured app connecting with wrong
password). Confirm the IPs being banned are not your own application servers
before setting a tight maxretry.
Scan detection
portsentry
What it matches: Supported PortSentry v2.0.7 history records. The whole-record matcher extracts the typed bracketed source address and preserves original event time. Ignored, pre-threshold and sensor-blocked records are excluded.
Log source: The PortSentry history file, conventionally /var/log/portsentry.log,
with source = "file" and timestamp = "iso8601". Syslog, stdout and journal
envelopes are outside this integration.
Qualified deployment: PortSentry v2.0.7 on Debian 13 with IPv4/IPv6 nftables
bans, expiry and restored connectivity. --connect was qualified in 0.4.3;
--stealth -m pcap -i <interface> (TCP SYN, FIN, NULL and XMAS scans, and UDP)
in 0.4.7. Legacy 1.2 and other 2.x versions stay unqualified. Automatic bans
cover the source across host ports and protocols.
Follow the PortSentry setup guide for the sensor settings,
enforcing jail and validation sequence. Sensor response actions must be disabled.
PortSentry recreates its history file at every start; choose the jail’s
on_truncation policy deliberately.
Meta
recidive
What it matches: Repeat offenders. Every ban enforced in another jail is
recorded in-process as one attempt against recidive — no log file, no
logpath (ADR-013). fail2zig jails shows SOURCE internal,
HEALTH ok. Restores, unbans, failed bans, and log-only would-bans do not
count.
Log file: None. The shipped default config includes a recidive jail with
no logpath.
Recommended jail settings (the shipped defaults):
[jails.recidive]
enabled = true
filter = "recidive"
source = "internal"
maxretry = 2 # 2 enforced bans in any jail → recidive escalation
findtime = 86400 # within a 24-hour window
bantime = 604800 # 1 week for persistent repeat offendersSummary table
| Filter | Category | Typical log path |
|---|---|---|
sshd | SSH | /var/log/auth.log, /var/log/secure |
nginx-http-auth | Web | /var/log/nginx/error.log |
nginx-limit-req | Web | /var/log/nginx/error.log |
nginx-botsearch | Web | /var/log/nginx/access.log |
apache-auth | Web | /var/log/apache2/error.log |
apache-badbots | Web | /var/log/apache2/access.log |
apache-overflows | Web | /var/log/apache2/error.log |
postfix | /var/log/mail.log | |
dovecot | /var/log/mail.log | |
courier | /var/log/mail.log | |
named-refused | DNS | /var/log/named/default |
vsftpd | FTP | /var/log/vsftpd.log |
proftpd | FTP | /var/log/proftpd/proftpd.log |
mysqld-auth | Database | /var/log/mysql/error.log |
recidive | Meta | None (in-process internal source) |
portsentry | Sensor | /var/log/portsentry.log (v2.0.7 history) |
Custom native rules
Native rules are JSON, not arbitrary TOML patterns or automatically imported fail2ban regexes. Rules support structured JSON or typed templates with predicates, exclusions and bounded correlation. A minimal template rule:
{
"id": "app-denied",
"source": "application",
"format": "template",
"template": "Denied login for {user:word} from {peer:ip} port {port:uint}",
"subject": "peer",
"conditions": [{ "field": "port", "number": 2201 }],
"exclude": [{ "field": "user", "text": "probe" }]
}Save as /etc/fail2zig/rules/application.json with administrator-controlled
permissions. Select it in a reviewed jail:
[jails.application]
filter = "application"
source = "file"
timestamp = "undated"
logpath = ["/var/log/application.log"]
rule_files = ["/etc/fail2zig/rules/application.json"]
enforce = falseStart by evaluating representative records offline:
fail2zig rule-test --file /path/to/sample.log --rule-file /etc/fail2zig/rules/application.jsonA record such as Denied login for alice from 203.0.113.7 port 2201 matches this
rule; a probe user is excluded and another port misses the condition. Review
matched, missed, ignored and rejected counts before changing log-only policy.
The 0.4.0 custom-rule path caps a rule document at 4 KiB and a record at 2 KiB, with bounded fields, segments and work. See the tagged rule grammar and parser architecture for the boundary.