Root Cause Diagnosis
Fail2ban tails the SSH auth log (/var/log/auth.log on Debian/Ubuntu, /var/log/secure on RHEL-family). When one IP exceeds maxretry failures within findtime, it inserts a firewall rule (iptables, nftables, ipset, or firewalld) that drops that IP for bantime. Bans are also written to the Fail2ban SQLite database and reinstated on service restart, so restarting the service does not clear them. If you are locked out of SSH, you need a session from a non-banned IP or the provider’s out-of-band console (VNC, KVM, serial) to run the unban commands.
Quick Fix
From the provider console or a different IP:
fail2ban-client status # list jails
fail2ban-client unban 203.0.113.50 # remove from all jails (Fail2ban 0.10+)
fail2ban-client set sshd unbanip 203.0.113.50 # per-jail form (also works on 0.9)
To prevent an immediate re-ban, whitelist the IP in the same session (Step 4).
If fail2ban-client is not in PATH, the aaPanel plugin installs it under a custom prefix. Locate it first:
command -v fail2ban-client || find / -name fail2ban-client -type f 2>/dev/null
Step-by-Step Resolution
1. Regain a shell
- Use the provider’s web console (VNC/KVM/serial). It bypasses the firewall entirely.
- Or connect from another IP: a mobile hotspot, VPN, or a jump host that is not banned.
- If the aaPanel web panel is still reachable, use its built-in terminal, since the ban usually covers only the SSH port.
2. Identify the jail and confirm the ban
fail2ban-client status
fail2ban-client status sshd
Sample output:
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| `- Total failed: 14
`- Actions
|- Currently banned: 1
`- Banned IP list: 203.0.113.50
The jail name may differ (ssh, sshd, ssh-iptables), so use the name from the first command. To search all jails for the IP:
for j in $(fail2ban-client status | sed -n 's/.*Jail list:\s*//p' | tr -d ','); do
fail2ban-client status "$j" | grep -q 203.0.113.50 && echo "banned in: $j"
done
Confirm from the log (find the path first, since the aaPanel plugin may not use the default):
fail2ban-client get logtarget
grep "203.0.113.50" /var/log/fail2ban.log | tail
3. Unban the IP
# All jails at once (0.10+)
fail2ban-client unban 203.0.113.50
# One jail (any version)
fail2ban-client set sshd unbanip 203.0.113.50
# Everything
fail2ban-client unban --all
If the plugin runs a non-default socket, pass it explicitly:
fail2ban-client -s /path/to/fail2ban.sock unban 203.0.113.50
Also check the recidive jail. It applies a much longer ban to repeat offenders, independent of sshd:
fail2ban-client status recidive
fail2ban-client set recidive unbanip 203.0.113.50
4. Whitelist the IP so it is not re-banned
Edit the jail file (/etc/fail2ban/jail.local on package installs, or the aaPanel plugin’s config directory, often under /www/server/fail2ban/). Do not edit jail.conf, since package updates overwrite it.
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.50 198.51.100.0/24
Apply without restarting:
fail2ban-client reload
fail2ban-client get sshd ignoreip
You can also set it at runtime, though it is lost on restart:
fail2ban-client set sshd addignoreip 203.0.113.50
5. Verify the firewall rule is gone
# iptables backend
iptables -S | grep -E 'f2b|fail2ban'
iptables -L f2b-sshd -n --line-numbers
# nftables backend
nft list ruleset | grep -iE 'f2b|203.0.113.50'
# ipset backend
ipset list | grep -A20 f2b
The IP should not appear. Then test from the affected machine:
ssh -v user@SERVER_IP -p SSH_PORT
6. Manual firewall removal (fallback)
Use this only if fail2ban-client cannot reach the daemon. Prefer the client, because manual edits leave Fail2ban’s internal state out of sync.
# iptables: delete by rule number
iptables -L f2b-sshd -n --line-numbers
iptables -D f2b-sshd 1
# ipset
ipset del f2b-sshd 203.0.113.50
# firewalld rich rule
firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="203.0.113.50" reject'
firewall-cmd --reload
7. Stop a restart from reinstating the ban
Fail2ban restores unexpired bans from its database on startup. If you must restart, stop first, clear the row, then start:
systemctl stop fail2ban
sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "DELETE FROM bans WHERE ip='203.0.113.50';"
systemctl start fail2ban
Confirm the database path with fail2ban-client get dbfile. Stopping the service also removes its firewall chains on most action types, which lifts every ban until restart.
Why This Fails for Some Users
- The block is not Fail2ban:
fail2ban-client statusshows nothing banned, yet SSH is still refused. Check in order:- aaPanel’s own firewall (Security page): remove any
droprule for the IP or SSH port. - Cloud security group or provider firewall.
- /etc/hosts.deny and
DenyUsers/AllowUsers/AllowGroupsin /etc/ssh/sshd_config. - CSF/LFD if installed alongside (
csf -g 203.0.113.50,csf -dr 203.0.113.50). MaxStartupsthrottling in sshd_config, which returns connection resets, not a hard ban.
- aaPanel’s own firewall (Security page): remove any
Failed to access socket path: the daemon is down or uses a non-default socket. Runsystemctl status fail2ban, orps aux | grep fail2banto find the process and its-sargument. With the daemon stopped, the ban rules usually no longer exist, so the block is elsewhere.- Fail2ban older than 0.10 (CentOS 7, Ubuntu 16.04): the global
unbansubcommand does not exist. Useset <jail> unbanip. These versions also run on Python 2, and--allis unavailable. - Wrong jail name:
Sorry but the jail 'sshd' does not existmeans the plugin named it differently. Use the exact name fromfail2ban-client status. - SSH port changed: the ban covers whichever port the jail was configured with. If you changed the SSH port in /etc/ssh/sshd_config but not the jail’s
portsetting, the ban still applies to the old port, and connections to the new port may be blocked by a separate firewall rule instead. - Shared, NAT, or CGNAT source IP: the ban may target an address shared by other users, so one failed-login burst locks everyone behind it. Whitelist the CIDR range, not a single address, and confirm your current public IP with
curl -s ifconfig.mefrom the affected client. - IPv6: if the client connects over IPv6, the ban is on the IPv6 address (or its /64 with some actions), and unbanning the IPv4 address has no effect. List bans with
fail2ban-client status sshdand unban the exact string shown. - Backend mismatch: on systems using
backend = systemd, ban timing and logs come from the journal (journalctl -u sshor-u sshd), not from auth.log. If neither exists, the jail never triggers. - Docker or NAT-forwarded services: container traffic traverses the
FORWARDchain, and aINPUT-only ban will not block it. In these setups, check theDOCKER-USERchain instead off2b-sshd. - Ban persists after unban: a
bantime.increment = truesetting with a largebantime.factormakes re-bans escalate quickly. Addignoreip, raisemaxretry, and use key-based login to stop repeat failures.