Guides

fail2ban for MySQL, MariaDB, PostgreSQL and MongoDB

A database port open to the internet gets password-sprayed like SSH. fail2ban can stop that, but only if the database logs failed logins with the client address. Most don't by default. This guide shows the logging setting and the jail for each one.

Updated 11 min readChecked against fail2ban 1.1.1

To protect MySQL or MariaDB with fail2ban, make the server log failed logins (log_error_verbosity = 3 on MySQL 8, log_warnings = 2 on MariaDB, the default there). Then enable the stock [mysqld-auth] jail with logpath set to your error log. The mysqld-auth filter matches Access denied for user '...'@'<IP>' and bans port 3306.

/etc/fail2ban/jail.local
[mysqld-auth]
enabled  = true
backend  = auto
logpath  = /var/log/mysql/error.log
maxretry = 5
bantime  = 1h

Before any of this: a database that only your app server uses shouldn't listen on the internet. bind-address = 127.0.0.1 (or a private address) plus a firewall rule beats any jail. Use fail2ban when remote access is a real requirement, as one layer among the fail2ban best practices.

Step 1: make MySQL or MariaDB log failed logins#

The filter needs a line like one of these in the log:

log
2026-10-25T18:36:10.123456Z 31 [Note] [MY-010926] [Server] Access denied for user 'root'@'203.0.113.42' (using password: YES)
2026-10-25 18:36:10 31 [Warning] Access denied for user 'root'@'203.0.113.42' (using password: YES)

The first is MySQL 8.x, the second MariaDB. Whether those lines appear depends on the log level.

MySQL 8 logs Access denied as a Note, and the default log_error_verbosity = 2 (errors and warnings) drops notes. Raise it:

/etc/mysql/mysql.conf.d/zz-fail2ban.cnf
[mysqld]
log_error_verbosity = 3
log_error           = /var/log/mysql/error.log

Restart MySQL: sudo systemctl restart mysql. Verbosity 3 also logs other informational notes, so the error log grows faster. Make sure logrotate handles it (the Ubuntu package does).

Step 2: find the log path#

System Default error log
Ubuntu / Debian, MySQL 8 /var/log/mysql/error.log
Debian / Ubuntu, MariaDB systemd journal (journalctl -u mariadb) unless log_error is set
RHEL / Rocky / Alma, MariaDB /var/log/mariadb/mariadb.log
RHEL / Rocky / Alma, MySQL 8 /var/log/mysql/mysqld.log
Fedora / Arch, MariaDB journal on most installs, or /var/log/mariadb/mariadb.log
Official Docker images container stdout (docker logs)

The jail's default logpath changed in fail2ban 1.1.1. It now tries /var/log/mariadb/mariadb.log, /var/log/mariadb/error.log, /var/log/mysql/mysqld.log, /var/log/mysql/error.log and /var/log/mysqld.log. Older versions point at the syslog daemon log (/var/log/daemon.log on Debian), which usually doesn't contain MySQL's messages at all. Set logpath yourself and you're independent of the version.

Ask the server where it logs: SELECT @@log_error;. A value of stderr means the journal under systemd.

Step 3: the jail#

File-based (MySQL 8, or MariaDB with log_error set):

/etc/fail2ban/jail.local
[mysqld-auth]
enabled  = true
backend  = auto
port     = 3306
logpath  = /var/log/mysql/error.log
maxretry = 5

backend = auto matters on Ubuntu 24.04, whose defaults-debian.conf sets backend = systemd for every jail. Without it, the jail would ignore your logpath (how backends work).

Journal-based (MariaDB on Debian/Ubuntu without log_error):

/etc/fail2ban/jail.local
[mysqld-auth]
enabled      = true
backend      = systemd
journalmatch = _SYSTEMD_UNIT=mariadb.service
filter       = mysqld-auth[_daemon="(?:mysqld|mariadbd)"]
port         = 3306
maxretry     = 5

The filter has no journalmatch, so the jail sets one. In the journal MariaDB 10.5+ lines are tagged mariadbd, not mysqld, and the stock filter only accepts mysqld as the daemon name. The _daemon parameter fixes that. Without it, journal lines such as db1 mariadbd[812]: ... Access denied for user ... don't match.

Step 4: test#

bash
# file
sudo fail2ban-regex /var/log/mysql/error.log mysqld-auth

# journal
sudo fail2ban-regex -m "_SYSTEMD_UNIT=mariadb.service" systemd-journal 'mysqld-auth[_daemon="(?:mysqld|mariadbd)"]'

Generate a failure first with a wrong password from another host: mysql -h db.example.com -u test -pwrong. Then reload and check:

bash
sudo fail2ban-client -t
sudo systemctl restart fail2ban
sudo fail2ban-client status mysqld-auth

Connections from localhost over the Unix socket are logged as 'user'@'localhost'. fail2ban can't ban a hostname, and you wouldn't want it to ban your own apps anyway. More on reading the regex output on fail2ban-regex.

PostgreSQL#

fail2ban has no stock PostgreSQL filter, and by default PostgreSQL doesn't put the client address on the failure line:

log
2026-10-25 18:36:10.123 UTC [4321] postgres@postgres FATAL:  password authentication failed for user "postgres"

Add the client host (%h) to log_line_prefix. On Debian/Ubuntu the packaged prefix is '%m [%p] %q%u@%d ', so extend it:

/etc/postgresql/16/main/conf.d/fail2ban.conf
log_line_prefix = '%m [%p] %q%u@%d %h '
log_hostname    = off

log_hostname must stay off so %h is an IP address. Reload PostgreSQL (sudo systemctl reload postgresql). Failures now look like this:

log
2026-10-25 18:36:10.123 UTC [4321] postgres@postgres 203.0.113.42 FATAL:  password authentication failed for user "postgres"
2026-10-25 18:36:11.456 UTC [4322] postgres@postgres 198.51.100.7 FATAL:  no pg_hba.conf entry for host "198.51.100.7", user "postgres", database "postgres", no encryption

log_connections = on isn't needed for this. It logs successful connection attempts on separate lines, which don't help the filter. Create a custom filter that matches the prefix above:

/etc/fail2ban/filter.d/postgresql.local
[Definition]
failregex = ^\s*(?:\S+ )?\[\d+\] \S*@\S* <HOST> FATAL:  password authentication failed for user "<F-USER>[^"]*</F-USER>"
            ^\s*(?:\S+ )?\[\d+\] (?:\S*@\S* )?(?:\S+ )?FATAL:  no pg_hba\.conf entry for host "<HOST>"
ignoreregex =

Both lines above match it, for IPv4 and IPv6 clients. Local socket connections ([local]) don't. Note the two spaces after FATAL:, which is how PostgreSQL writes it.

/etc/fail2ban/jail.local
[postgresql]
enabled  = true
filter   = postgresql
backend  = auto
port     = 5432
logpath  = /var/log/postgresql/postgresql-*-main.log
maxretry = 5

On RHEL-family systems the log is under the data directory, for example logpath = /var/lib/pgsql/data/log/postgresql-*.log. Test with sudo fail2ban-regex /var/log/postgresql/postgresql-16-main.log /etc/fail2ban/filter.d/postgresql.local.

If you change log_line_prefix later, the filter must change with it. The regex is tied to the prefix format.

MongoDB#

The stock mongodb-auth filter and [mongodb-auth] jail (port 27017, logpath = /var/log/mongodb/mongodb.log) were written for MongoDB's old plain-text log format. MongoDB 4.4 and newer log structured JSON, which the stock filter doesn't match. A failed login in a current version is a JSON object with "c":"ACCESS", "id":20249, "msg":"Authentication failed" and the client address in attr.remote (some versions use client).

A local filter for the JSON format:

/etc/fail2ban/filter.d/mongodb-json.local
[Definition]
failregex = ^.*"c":"ACCESS",\s*"id":20249,.*"msg":"Authentication failed".*"(?:remote|client)":"<ADDR>:\d+"
datepattern = {^LN-BEG}\{"t":\{"\$date":"%%Y-%%m-%%dT%%H:%%M:%%S\.%%f%%z"\}
ignoreregex =

It matches lines of this shape:

json
{"t":{"$date":"2026-10-25T18:36:10.123+00:00"},"s":"I",  "c":"ACCESS",   "id":20249,   "ctx":"conn12","msg":"Authentication failed","attr":{"mechanism":"SCRAM-SHA-256","speculative":false,"principalName":"admin","authenticationDatabase":"admin","remote":"203.0.113.42:51234","extraInfo":{},"error":"AuthenticationFailed: SCRAM authentication failed, storedKey mismatch"}}
/etc/fail2ban/jail.local
[mongodb-json]
enabled  = true
filter   = mongodb-json
backend  = auto
port     = 27017
logpath  = /var/log/mongodb/mongod.log
maxretry = 5

Check one of your own failure lines in the regex tester before enabling it, because the field name holding the address has varied between releases. Drivers that try speculative authentication can also log a failed attempt during a normal handshake, so keep maxretry above 1 and whitelist your app servers. Packaged MongoDB logs to /var/log/mongodb/mongod.log, not the mongodb.log the stock jail expects.

Built-in filter reference: mysqld-auth, mongodb-auth, mssql-auth and more#

The built-in filters this guide uses, exactly as fail2ban 1.1.1 ships them — with the log lines they match and a link to test them. Looking for something else? See filters for web apps and admin panels, or the full filter library.

mysqld-auth MySQL / MariaDB

The mysqld-auth filter bans hosts with repeated 'Access denied' errors in the MySQL/MariaDB error log. File: /etc/fail2ban/filter.d/mysqld-auth.conf; jail [mysqld-auth]; log on Debian/Ubuntu: /var/log/mariadb/mariadb.log.

Log lines it bans (from fail2ban's test suite):

  1. 130324 0:04:00 [Warning] Access denied for user 'root'@'192.168.1.35' (using password: NO)
  2. 130324 8:24:09 [Warning] Access denied for user 'root'@'220.95.238.171' (using password: NO)
  3. 130324 17:56:13 [Warning] Access denied for user 'root'@'61.160.223.112' (using password: NO)
Show the failregex fail2ban compiles (1 line)
failregex (1)
^(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?mysqld(?:\(\S+\))?[\]\)]?:?|[\[\(]?mysqld(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?(?:(?:\d{6}|\d{4}-\d{2}-\d{2})[ T]\s?\d{1,2}:\d{2}:\d{2} )?(?:\d+ )?\[\w+\] (?:\[[^\]]+\] )*Access denied for user '<F-USER>[^']+</F-USER>'@'<HOST>'(?:\s+(?:to database '[^']*'|\(using password: (?:YES|NO)\)){1,2})?\s*$

Test mysqld-auth in the browser or on the server: sudo fail2ban-regex /var/log/mariadb/mariadb.log mysqld-auth · source

mongodb-auth MongoDB

The mongodb-auth filter bans failed MongoDB authentication attempts. File: /etc/fail2ban/filter.d/mongodb-auth.conf; jail [mongodb-auth]; log on Debian/Ubuntu: /var/log/mongodb/mongodb.log.

Log lines it bans (from fail2ban's test suite):

  1. 2016-11-20T00:04:00.111+0100 [conn1] end connection 192.0.2.35:53276 (0 connections now open)
  2. 2016-11-20T00:24:00.111+0100 [conn5] end connection 192.0.2.171:53276 (0 connections now open)
  3. 2016-11-20T00:24:00.111+0100 [conn334] end connection 192.0.2.176:53276 (0 connections now open)
Show the failregex fail2ban compiles (1 line)
failregex (1)
^\s+\[conn(?P<__connid>\d+)\] Failed to authenticate [^\n]+<SKIPLINES>\s+\[conn(?P=__connid)\] end connection <HOST>

Test mongodb-auth in the browser or on the server: sudo fail2ban-regex /var/log/mongodb/mongodb.log mongodb-auth · source

mssql-auth Microsoft SQL Server on Linux

The mssql-auth filter bans failed SQL Server logins. File: /etc/fail2ban/filter.d/mssql-auth.conf; jail [mssql-auth]; log on Debian/Ubuntu: /var/opt/mssql/log/errorlog.

Log lines it bans (from fail2ban's test suite):

  1. 2020-02-24 16:05:21.00 Logon Login failed for user 'Backend'. Reason: Could not find a login matching the name provided. [CLIENT: 192.0.2.1]
  2. 2020-02-24 16:30:25.88 Logon Login failed for user '===)jf02hüas9ä##22f'. Reason: Could not find a login matching the name provided. [CLIENT: 192.0.2.2]
  3. 2020-02-24 16:31:12.20 Logon Login failed for user ''. Reason: An attempt to login using SQL authentication failed. Server is configured for Integrated authentication only. [CLIENT: 192.0.2.3]
Show the failregex fail2ban compiles (1 line)
failregex (1)
^\s*Logon\s+Login failed for user '<F-USER>(?:[^']*|.*)</F-USER>'\. [^'\[]+\[CLIENT: <ADDR>\]$

Test mssql-auth in the browser or on the server: sudo fail2ban-regex /var/opt/mssql/log/errorlog mssql-auth · source

phpmyadmin-syslog phpMyAdmin

The phpmyadmin-syslog filter bans failed phpMyAdmin logins (phpMyAdmin logging to syslog). File: /etc/fail2ban/filter.d/phpmyadmin-syslog.conf; jail [phpmyadmin-syslog]; log on Debian/Ubuntu: /var/log/auth.log.

Log lines it bans (from fail2ban's test suite):

  1. Aug 22 14:50:22 eurostream phpMyAdmin[16358]: user denied: root (mysql-denied) from 192.0.2.1
Show the failregex fail2ban compiles (1 line)
failregex (1)
^(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?phpMyAdmin(?:\(\S+\))?[\]\)]?:?|[\[\(]?phpMyAdmin(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?user denied: (?:\S+|.*?) \(mysql-denied\) from <HOST>\s*$

Test phpmyadmin-syslog in the browser or on the server: sudo fail2ban-regex /var/log/auth.log phpmyadmin-syslog · source

FAQ#

How do I use fail2ban with MySQL?#

Set log_error_verbosity = 3 in MySQL 8's [mysqld] section so Access denied notes are logged, then enable the [mysqld-auth] jail with logpath = /var/log/mysql/error.log and backend = auto. Test with fail2ban-regex /var/log/mysql/error.log mysqld-auth after a failed remote login.

Why does the fail2ban mysqld-auth jail not ban anything?#

Usually the failures aren't logged or aren't where the jail looks. MySQL 8 needs log_error_verbosity = 3. MariaDB on Debian logs to the journal unless log_error is set. Older fail2ban versions point the jail at /var/log/daemon.log. Run SELECT @@log_error; and set logpath to match.

Does fail2ban work with MariaDB?#

Yes. MariaDB logs [Warning] Access denied for user ... at the default log_warnings = 2. With a log file, use the stock mysqld-auth jail. With the journal, set backend = systemd, journalmatch = _SYSTEMD_UNIT=mariadb.service and filter = mysqld-auth[_daemon="(?:mysqld|mariadbd)"].

Is there a fail2ban filter for PostgreSQL?#

Not in fail2ban itself. Add %h to PostgreSQL's log_line_prefix (for example '%m [%p] %q%u@%d %h ') so failed logins include the client IP, then match FATAL: password authentication failed for user with a short custom filter and ban port 5432.

Does the fail2ban mongodb-auth filter work with MongoDB 6 or 7?#

No. The stock filter expects the plain-text log format used before MongoDB 4.4. Current versions log JSON, where a failed login is event id 20249 with "msg":"Authentication failed". Use a JSON-aware local filter that captures the address from the remote field.

Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.