# fail2ban for MySQL, MariaDB, PostgreSQL and MongoDB

> Stop database brute force with fail2ban: the mysqld-auth jail, Access denied logging for MySQL 8 and MariaDB, log paths, plus PostgreSQL and MongoDB filters.
>
> Source: https://fail2ban.dev/guides/mysql/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

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](/guides/mysql/#filter-mysqld-auth) matches `Access denied for user '...'@'<IP>'` and bans port 3306.

```ini
[mysqld-auth]
enabled  = true
backend  = auto
logpath  = /var/log/mysql/error.log
maxretry = 5
bantime  = 1h
```

## Step 1: make MySQL or MariaDB log failed logins

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

```log
2026-10-25 18:36:10 31 [Warning] Access denied for user 'root'@'203.0.113.42' (using password: YES)
```


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

```ini
[mysqld]
log_error_verbosity = 3
log_error           = /var/log/mysql/error.log
```
#### MariaDB
MariaDB logs access-denied errors at `log_warnings = 2`, which has been the default since 10.2.4. Check it with `SELECT @@log_warnings;`. What usually differs is *where* it logs. On Debian and Ubuntu the packaged config leaves `log_error` commented out, so under systemd the errors go to the **journal**, not a file. Either read the journal (see below) or enable a file:

```ini
[mysqld]
log_warnings = 2
log_error    = /var/log/mysql/error.log
```
#### MySQL 5.7 / older MariaDB
Use `log_warnings = 2` (`log-warnings = 2` in older configs) in the `[mysqld]` section, and check `log_error` points at a file fail2ban can read.

## 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):

```ini
[mysqld-auth]
enabled  = true
backend  = auto
port     = 3306
logpath  = /var/log/mysql/error.log
maxretry = 5
```

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

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

## Step 4: test

```bash
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)"]'
```

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

## PostgreSQL

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

```log
```

```ini
log_line_prefix = '%m [%p] %q%u@%d %h '
log_hostname    = off
```

```log
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
```

```ini
[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 =
```

```ini
[postgresql]
enabled  = true
filter   = postgresql
backend  = auto
port     = 5432
logpath  = /var/log/postgresql/postgresql-*-main.log
maxretry = 5
```

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](/guides/mysql/#filter-mongodb-auth) 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:

```ini
[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 =
```

```json
```
[mongodb-json]
enabled  = true
filter   = mongodb-json
backend  = auto
port     = 27017
logpath  = /var/log/mongodb/mongod.log
maxretry = 5
```

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

{{component:filter-ref:mysqld-auth.mongodb-auth.mssql-auth.phpmyadmin-syslog}}

## 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.
