Back to tutorials
Tutorial

Fail2Ban Setup Tutorial: Stop SSH and WordPress Brute-Force Attacks on an Ubuntu VPS (2026)

Fail2Ban setup tutorial for Ubuntu VPS in 2026: protect SSH and WordPress with jails, filters, logs, and safe bans.

By Anurag Singh
Updated on Jun 05, 2026
Category: Tutorial
Share article
Fail2Ban Setup Tutorial: Stop SSH and WordPress Brute-Force Attacks on an Ubuntu VPS (2026)

A firewall and SSH keys cut your risk fast. They won’t stop constant password guessing from burning CPU or bloating logs.

This Fail2Ban setup tutorial shows how to install, configure, and verify Fail2Ban on an Ubuntu VPS. It then extends coverage to WordPress logins and XML-RPC. You’ll end with bans you can justify, logs you can audit, and settings that won’t lock you out.

This guide targets Ubuntu Server 24.04 LTS and Fail2Ban 1.0+ (common in Ubuntu repos in 2026). The same commands generally work on Ubuntu 22.04 LTS. The paths and systemd units referenced here are the same.

Before you start: what you need (and what you should double-check)

Confirm these three basics before you touch anything:

  • Root or sudo access on the VPS.
  • A working firewall policy (UFW or nftables/iptables). If you haven’t locked down ports yet, follow this: UFW firewall setup tutorial for Ubuntu VPS.
  • SSH access you won’t lose: at least one key-based login, plus a second session kept open while you test.

If you’re setting this up on fresh infrastructure, a HostMyCode VPS gives you the control and log visibility Fail2Ban needs.

On shared hosting, you typically can’t manage system firewall rules or run daemons that apply bans.

Install Fail2Ban on Ubuntu (systemd + default jail)

Update packages, install Fail2Ban, then enable it at boot:

sudo apt update
sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban --no-pager

Then confirm the client can talk to the server socket:

sudo fail2ban-client ping
sudo fail2ban-client status

You should see Server replied: pong.

The jail list may be empty until you create your own config. That’s normal.

Understand where Fail2Ban reads logs (and pick the right backend)

On Ubuntu, services usually log to one of two places:

  • systemd journal (journald)
  • traditional log files like /var/log/auth.log plus your web server access/error logs

Fail2Ban supports both.

On modern Ubuntu, the systemd backend is often the least fragile choice. It keeps working even if file-based log paths change. We’ll set that explicitly.

Create a safe baseline config (don’t edit jail.conf)

Ubuntu ships defaults in /etc/fail2ban/jail.conf. Don’t edit it.

Put your overrides in /etc/fail2ban/jail.local so package updates don’t overwrite your changes.

sudo nano /etc/fail2ban/jail.local

Paste a baseline that protects SSH without going overboard:

[DEFAULT]
# Prefer systemd journal on Ubuntu
backend = systemd

# Ban action: use UFW if you manage firewall with UFW.
# If you don't use UFW, change to iptables/nftables action later.
banaction = ufw

# Ban timing: tune for your risk tolerance
findtime = 10m
maxretry = 5
bantime  = 1h

# Never ban yourself/your office/VPN IPs. Add your static IPs here.
ignoreip = 127.0.0.1/8 ::1

# Email alerts are optional. Keep it off unless you have mail configured.
destemail = root@localhost
sender = fail2ban@localhost
mta = sendmail

action = %(action_)s

[sshd]
enabled = true
# On Ubuntu with OpenSSH, systemd backend usually works without logpath.
# If you switch backends, you may need: logpath = /var/log/auth.log

Why these values? A one-hour ban quiets the noise. It also avoids most “I’m locked out” incidents.

If your server sees constant bot traffic, raise bantime to 6–24 hours after you confirm the filter is behaving.

Restart Fail2Ban and check the SSH jail:

sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

Make sure bans actually work (UFW integration test)

Fail2Ban can look healthy and still fail to block anything. This usually happens when the firewall action doesn’t match your setup.

Test it once. Then move on.

1) Confirm UFW is running:

sudo ufw status verbose

2) Do a safe test: keep your own IP in ignoreip, then ban a dummy IP manually. Use an address you don’t control (documentation ranges are fine):

sudo fail2ban-client set sshd banip 203.0.113.50
sudo ufw status numbered | sed -n '1,120p'
sudo fail2ban-client set sshd unbanip 203.0.113.50

While the IP is banned, you should see a temporary UFW rule.

If nothing shows up, your banaction doesn’t line up with your firewall tooling.

Harden SSH the right way (Fail2Ban is not your first line)

Fail2Ban is a backstop. It is not a substitute for sane SSH settings.

Make sure these are true before you tighten thresholds:

  • SSH uses key authentication for admins.
  • PasswordAuthentication is off (or at least restricted).
  • Root login is disabled, or root uses keys only.

If you want a practical hardening checklist, follow: VPS hardening tutorial for Ubuntu.

It pairs well with Fail2Ban because it reduces lockout risk and cuts down on “real” authentication failures.

Add WordPress protection: wp-login.php, XML-RPC, and admin probes

WordPress brute-force traffic usually concentrates on:

  • /wp-login.php (login form)
  • /xmlrpc.php (legacy remote publishing, also used for pingbacks)
  • /wp-admin/ (redirect loops and auth prompts)

Fail2Ban can react to repeated 401/403 responses. It only works if your logs are consistent and your filter matches real log lines.

The most predictable approach is simple: ban from web server access logs based on repeated login-related requests.

Step 1: confirm you have usable access logs

If you run Nginx, check:

sudo ls -lah /var/log/nginx/
sudo tail -n 50 /var/log/nginx/access.log

If you run Apache:

sudo ls -lah /var/log/apache2/
sudo tail -n 50 /var/log/apache2/access.log

If you’re tuning Nginx for PHP and WordPress, keep logging enabled for the vhost you care about.

You don’t need noisy logs, but you do need usable ones.

This pairs well with: Nginx performance optimization tutorial.

Step 2: create a WordPress filter

Create a custom Fail2Ban filter that matches repeated requests to wp-login.php and xmlrpc.php.

Target requests that return 200/401/403 responses.

sudo nano /etc/fail2ban/filter.d/wordpress.conf

Paste this filter (works with common combined log formats for Nginx/Apache):

[Definition]
# Match requests to wp-login.php and xmlrpc.php from an IP
# This catches typical brute force patterns without parsing WordPress internals.
failregex = ^<HOST> .* \"(POST|GET) /(wp-login\.php|xmlrpc\.php).*\" (200|401|403) .* 

ignoreregex =

Note: If your access logs are JSON (common in some newer Nginx setups), this regex won’t match.

In that case, switch the protected vhost back to a standard log_format, or write a JSON-aware regex.

Step 3: enable a WordPress jail in jail.local

Add this to /etc/fail2ban/jail.local (below your SSH jail):

[wordpress]
enabled = true
filter = wordpress

# Adjust to your stack
# Nginx default:
logpath = /var/log/nginx/access.log
# Apache default would be:
# logpath = /var/log/apache2/access.log

port = http,https
findtime = 10m
maxretry = 10
bantime = 2h

Restart Fail2Ban and verify the jail loads:

sudo systemctl restart fail2ban
sudo fail2ban-client status wordpress

While you test a few failed logins, watch Fail2Ban’s log in another terminal:

sudo tail -f /var/log/fail2ban.log

If you’re dealing with high-volume bots, edge rate limiting helps a lot.

If you use Nginx, pair this with: Nginx rate limiting tutorial. Nginx cuts bursts immediately; Fail2Ban handles repeat offenders with longer bans.

Reduce false positives: ignore lists, trusted proxies, and real client IPs

Most Fail2Ban headaches come from configuration basics. These three checks prevent the common mistakes.

1) Add your admin IPs to ignoreip

If you have a fixed office/VPN IP, add it:

ignoreip = 127.0.0.1/8 ::1 198.51.100.10 203.0.113.25

If your IP changes frequently, don’t “solve” it by ignoring a giant ISP range.

Use a VPN with a stable exit IP instead.

2) If you’re behind a reverse proxy or CDN, fix the “real IP” first

If your VPS sits behind Cloudflare or another proxy, your access logs may contain proxy IPs instead of the real client address.

Fail2Ban will then ban the proxy. That’s pointless, and it can be catastrophic.

For Nginx, you typically configure real_ip_header and set_real_ip_from.

Also make sure your log format uses $remote_addr after real IP processing.

Validate it by making a request from a known client IP. Confirm that same IP appears in your access logs.

3) Confirm WordPress jail bans the attacker IP, not yours

Run:

sudo fail2ban-client get wordpress logpath
sudo fail2ban-client get wordpress maxretry
sudo fail2ban-client status wordpress

If you see surprising bans, shorten the blast radius while you debug.

Set bantime = 10m and watch behavior. Increase bans once you’re confident.

Don’t jump straight to one-day bans on day one.

Operational workflow: inspect bans, unban safely, and keep an audit trail

These are the commands you’ll reach for when someone gets blocked and you need answers quickly.

  • List jails:
    sudo fail2ban-client status
  • See banned IPs for a jail:
    sudo fail2ban-client status sshd
    sudo fail2ban-client status wordpress
  • Unban an IP:
    sudo fail2ban-client set wordpress unbanip 203.0.113.77
  • Search Fail2Ban decisions:
    sudo grep -E "\[(sshd|wordpress)\]" /var/log/fail2ban.log | tail -n 80

If you want visibility while tuning protections, add lightweight monitoring early.

This pairs nicely with: VPS monitoring tutorial. You’ll quickly see whether bans reduce load and log churn.

Optional: tune bantime with progressive bans (a practical middle ground)

Some sources will come back all day. A fixed one-hour ban helps, but the same IP can return on schedule.

Fail2Ban supports incremental bans (often called “recidive”-style escalation). Feature names can vary by packaging.

To keep this straightforward, add a second jail. It watches Fail2Ban’s own log and escalates repeat offenders.

Create a jail that uses the built-in recidive filter if available (many installs include it). Add to jail.local:

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = ufw
findtime = 1d
bantime  = 7d
maxretry = 5

Restart and verify:

sudo systemctl restart fail2ban
sudo fail2ban-client status recidive

If your package doesn’t include the recidive filter, you can still build escalation with a custom filter.

It works, but it’s easy to overtune. Get SSH and WordPress stable first.

Troubleshooting checklist (the failures you’ll actually see)

  • Jail shows “0 currently banned” during obvious attacks
    Check your logpath exists and updates. Then verify your regex matches: sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress.conf
  • Fail2Ban runs but doesn’t create firewall rules
    Confirm your firewall action: sudo fail2ban-client get sshd banaction. If you don’t use UFW, switch banaction to an iptables/nftables-compatible action.
  • You banned yourself
    Use your VPS console (provider console) if SSH is blocked, then unban: sudo fail2ban-client set sshd unbanip YOUR.IP.ADDR. Add your admin IP to ignoreip after.
  • Everything is behind Cloudflare and bans do nothing
    Fix real IP handling in your web server logs first; Fail2Ban can’t ban what it can’t see.
  • High CPU from log scanning
    Reduce the number of log files scanned, avoid wildcards over huge rotated logs, and prefer systemd backend for auth logs. Consider raising findtime and maxretry slightly rather than scanning more aggressively.

Production-ready settings you can copy (quick template)

If you want a compact starting point, this set is usually safe for small business sites:

  • sshd: findtime=10m, maxretry=5, bantime=1h
  • wordpress: findtime=10m, maxretry=10, bantime=2h
  • recidive: findtime=1d, maxretry=5, bantime=7d

After a week, review /var/log/fail2ban.log.

If the same networks keep returning, extend bans or add Nginx rate limiting on only the sensitive endpoints.

Summary: a practical way to keep bots from wasting your VPS

You now have Fail2Ban installed, an SSH jail with predictable behavior, and a WordPress jail focused on the endpoints attackers hit hardest.

Keep it measurable: confirm the logs you’re parsing, verify firewall rules appear, and read Fail2Ban’s own decisions before you tighten anything.

If you want this protection on infrastructure you control (root access, clean networking, predictable performance), run it on a HostMyCode VPS.

If you’d rather not handle server baselines and security maintenance yourself, managed VPS hosting is the simpler option.

If you’re moving WordPress (or multiple client sites) off shared hosting, Fail2Ban is one of the first tools that gives you real control. Start with a HostMyCode VPS if you want to manage the stack yourself, or choose managed VPS hosting if you want HostMyCode to maintain the server baseline and keep the fundamentals tight.

FAQ

Can I use Fail2Ban on shared hosting?

Usually no. Shared plans rarely give you access to system firewall rules or the ability to run and manage the Fail2Ban daemon. If you need IP bans and custom jails, a VPS is the right fit.

What’s better for WordPress attacks: Fail2Ban or Nginx rate limiting?

They solve different problems. Nginx rate limiting cuts request bursts immediately, often before PHP runs. Fail2Ban is better for longer-lived bans based on patterns over time.

Will Fail2Ban block legitimate users?

It can if thresholds are too strict, or if your logs record proxy IPs instead of real client IPs. Start with moderate values and confirm your logs show the actual remote address.

Where do I see why an IP was banned?

Check /var/log/fail2ban.log and the jail status output: sudo fail2ban-client status <jail>. If your filter doesn’t match real traffic, use fail2ban-regex to test it against real log lines.