Failed WordPress logins do not result in an HTTP 400-series status code. There are some very good reasons for this, and it is a conscious decision on the part of the WordPress developers to return an HTTP 200. There is a long and interesting discussion on this on the WordPress bug tracker.
While this makes it slightly more difficult to programmatically detect failed logins, it is still possible without altering your WordPress install. WordPress login attempts are submitted via POST request to wp-login.php or xmlrpc.php. A failed login results in an HTTP 200 that returns the user to the same login page. A successful login results in an HTTP 302 redirect to the originally requested page or the admin dashboard. Ironically, this means we can rely on HTTP 200 as an indicator of a failed login.
To create a jail using this method, simply add a .conf file to /etc/fail2ban/filter.d. I chose to name mine wordpress-login.conf. The contents will look like this:
failregex = ^<HOST> -.*"POST /(wp-login|xmlrpc)\.php.* HTTP/.*" 200 .*$
Note that this regular expression is written specifically for the Apache access log. If you are using another web server, alter the expression accordingly. Just ensure you are looking for POSTs to wp-login.php or xmlrpc.php that result in an HTTP 200.
Once you’ve added your filter, update your jail.local to enable it:
enabled = true
port = http,https
logpath = %(apache_access_log)s
Restart fail2ban for the new configuration to take effect.
You can test by failing a login and then checking /var/log/fail2ban.log for the detection:
INFO [wordpress-login] Found 192.168.0.15 - 2020-01-29 20:58:01