Start typing to search across invoices, services, domains, tickets, and more...
As soon as a server has a public IP, bots start guessing SSH passwords within minutes, and websites get scanned for admin pages and hammered with requests. Fail2ban watches your logs and, when an IP fails too often or requests too fast, bans it through the firewall automatically. This guide covers installing Fail2ban on Debian/Ubuntu and Rocky Linux/AlmaLinux, writing jail.local, enabling the sshd jail against SSH brute force, protecting websites with the nginx-limit-req and nginx-http-auth jails, ufw/firewalld integration, unbanning and whitelisting.
On Debian/Ubuntu install from the standard repositories together with python3-systemd so Fail2ban can read SSH logins from the systemd journal (Debian 12 and later no longer create /var/log/auth.log by default). On Rocky/AlmaLinux Fail2ban comes from EPEL, and the fail2ban-firewalld package makes it ban through firewalld.
# Debian / Ubuntu
apt update
apt install -y fail2ban python3-systemd
# Rocky Linux / AlmaLinux (EPEL)
dnf install -y epel-release
dnf install -y fail2ban fail2ban-firewalld
systemctl enable --now fail2ban
fail2ban-client version
Never edit jail.conf, which is replaced on upgrades; keep all changes in jail.local. The configuration below bans an IP for one hour after three failed logins within ten minutes, and repeat offenders get progressively longer bans of up to a week. If you moved SSH to another port (see our change SSH port tutorial), set port to that number, otherwise the ban will not cover the real port.
# Never edit jail.conf (it is overwritten on upgrade); use jail.local
cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
# Your own IPs are never banned (office/home IP, monitoring, other servers)
ignoreip = 127.0.0.1/8 ::1 198.51.100.20 203.0.113.0/24
bantime = 1h
findtime = 10m
maxretry = 5
# Repeat offenders get longer bans, up to one week
bantime.increment = true
bantime.maxtime = 1w
[sshd]
enabled = true
# use your custom port if you changed it, e.g. port = 2222
port = ssh
backend = systemd
maxretry = 3
EOF
fail2ban-client -t # test the configuration
systemctl restart fail2ban
fail2ban-client status
fail2ban-client status sshd
banaction decides how Fail2ban blocks an address. Use ufw on Ubuntu/Debian with ufw; on Rocky/AlmaLinux the fail2ban-firewalld package already selects firewalld rich rules; without either firewall use nftables-multiport. Choose exactly one, put it in the [DEFAULT] section of jail.local and restart Fail2ban (see our Linux firewall tutorial for firewall basics).
# Debian / Ubuntu with ufw - add to [DEFAULT] in /etc/fail2ban/jail.local
banaction = ufw
# Rocky / AlmaLinux with firewalld - set automatically by fail2ban-firewalld
# (/etc/fail2ban/jail.d/00-firewalld.conf), or explicitly:
banaction = firewallcmd-rich-rules
# Plain nftables without ufw/firewalld
banaction = nftables-multiport
# Check that bans really reach the firewall
ufw status numbered | head
firewall-cmd --list-rich-rules
nft list ruleset | grep -A5 f2b
The nginx-limit-req jail relies on Nginx's limit_req module: when a client exceeds the rate, Nginx writes "limiting requests" to error.log and Fail2ban bans the client. nginx-http-auth watches failed Basic Auth logins, which suits admin directories. Tune the rate to your traffic so real visitors are not caught.
# /etc/nginx/nginx.conf, inside http { }
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
# inside the server { } or location { } you want to protect
limit_req zone=perip burst=20 nodelay;
# protected area with basic auth (optional)
location /admin/ {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
nginx -t && systemctl reload nginx
A separate file in jail.d keeps the Nginx jails tidy. Before relying on them, run fail2ban-regex to confirm that the filter actually matches lines in your log. Control panels such as aaPanel store Nginx logs elsewhere, so adjust logpath to the real location.
cat > /etc/fail2ban/jail.d/nginx.local <<'EOF'
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
[nginx-limit-req]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
findtime = 1m
maxretry = 10
bantime = 2h
EOF
# Check that the filter matches lines in your log
fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-limit-req.conf
fail2ban-client reload
fail2ban-client status nginx-limit-req
You can unban from one jail or from all of them. addignoreip lasts only until Fail2ban restarts; for a permanent whitelist add the IP to ignoreip in jail.local and reload. If you banned yourself and cannot SSH in, connect from another network (for example a phone hotspot), use the console in the client area if available, or open a ticket.
fail2ban-client status sshd # list banned IPs
fail2ban-client set sshd unbanip 198.51.100.20 # unban from one jail
fail2ban-client unban 198.51.100.20 # unban from all jails
fail2ban-client unban --all # clear every ban
fail2ban-client set sshd addignoreip 198.51.100.20 # whitelist until restart
# Permanent whitelist: add the IP to "ignoreip" in jail.local, then
fail2ban-client reload
tail -f /var/log/fail2ban.log # watch bans live
This is typical on Debian 12 and later, which no longer write auth.log. Set backend = systemd in the sshd section and install python3-systemd. fail2ban-client -t shows the exact error.
Usually banaction does not match the firewall in use, or port differs from the real SSH port. Use the commands in Step 3 to check that matching rules exist.
No. Fail2ban only slows brute force down. Disabling password login and using SSH keys is the real fix; use both together for the best protection.
Still stuck after following these steps? Open a support ticket and the IMIDC 24/7 technical team will help. Include the server IP, OS version, the commands you ran and the full error output so we can pinpoint the issue faster.