Securing Linux with fail2ban
Run this on any server with SSH exposed to the internet:
sudo lastb | wc -l
That shows you the number of failed login attempts. On a server I set up last month, that number was 12,847. In three days. From IP addresses in countries I’ve never been to, trying usernames like admin, ubuntu, root, test, oracle, and my personal favorite, git.
These aren’t targeted attacks. They’re automated bots scanning the internet for servers with weak passwords. They try thousands of combinations per day, and they never stop.
Fail2ban watches your log files for these patterns and bans the offending IPs by updating your firewall rules. It’s one of the simplest security improvements you can make.
Installation
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
That’s it for the install. The configuration is where the interesting stuff happens.
Configuration structure
Don’t edit /etc/fail2ban/jail.conf directly. Package updates will overwrite it. Instead, create a local override:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Or better, create files in /etc/fail2ban/jail.d/:
sudo nano /etc/fail2ban/jail.d/custom.conf
The .d/ directory approach keeps your customizations clean and separate.
Protecting SSH
This is the first thing to configure:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
What those mean:
maxretry = 3, three failed attempts triggers a banfindtime = 600, the window for counting failures is 10 minutesbantime = 3600, ban lasts 1 hour (3600 seconds)
So: three wrong passwords in 10 minutes, and that IP is blocked for an hour.
For incremental punishment (each offense gets a longer ban):
bantime = 10m
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 4w
First offense: 10 minutes. Second: 20 minutes. Third: 40 minutes. Caps at 4 weeks. I like this approach because it’s harsh on repeat offenders but forgiving of honest mistakes.
Protecting other services
Fail2ban ships with filters for many common services:
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600
[postfix]
enabled = true
port = smtp,ssmtp
filter = postfix
logpath = /var/log/mail.log
maxretry = 5
bantime = 3600
For web login forms, give users more chances. People mistype passwords. maxretry = 5 or even 10 is more reasonable than 3 for a login page.
Custom filters
If your application isn’t covered by the built-in filters, write your own. Filters live in /etc/fail2ban/filter.d/.
Create a custom filter:
sudo nano /etc/fail2ban/filter.d/myapp.conf
[Definition]
failregex = ^.*Failed login from <HOST>.*$
^.*Authentication error from <HOST>.*$
ignoreregex =
The <HOST> placeholder matches an IP address. Test your filter against a real log file:
sudo fail2ban-regex /var/log/myapp.log /etc/fail2ban/filter.d/myapp.conf
This shows you which lines match and which don’t. Essential for debugging regex patterns (which, let’s be honest, always need debugging).
Ban actions
The default action adds the banned IP to iptables. You can also send email notifications:
action = %(action_mwl)s
This sends an email with the relevant log lines when a ban happens. Useful if you want to know what’s being blocked without checking the fail2ban logs.
Managing bans
Check overall status:
sudo fail2ban-client status
Check a specific jail:
sudo fail2ban-client status sshd
Manually ban an IP:
sudo fail2ban-client set sshd banip 192.168.1.100
Unban:
sudo fail2ban-client set sshd unbanip 192.168.1.100
List banned IPs:
sudo fail2ban-client set sshd banip list
The recidive jail
This is clever: ban repeat offenders across all jails.
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
bantime = 604800 # 1 week
findtime = 86400 # 1 day
maxretry = 3
If an IP gets banned three times in a day (from any jail), it gets banned for a week. This catches the bots that try different services after getting blocked from SSH.
The ignoreip setting
Add your own IP to the ignore list so you don’t lock yourself out:
[DEFAULT]
ignoreip = 127.0.0.1/8 your.trusted.ip
I’ve locked myself out before. It’s embarrassing, especially when you don’t have console access and have to call someone to unban you. Set this up first.
Common issues
Editing jail.conf instead of jail.local. Your changes get overwritten on package updates. Use .local or .d/ files.
Wrong log paths. The logpath has to match where your service actually logs. Different distributions put logs in different places. Check before configuring.
Aggressive settings on web forms. Three retries for SSH is fine. Three retries for a web login form means frustrated users. Adjust maxretry based on the service.
Not checking fail2ban after setup. It’s set-and-forget in terms of operation, but verify it’s working: sudo fail2ban-client status sshd should show active bans if the server has been online for a while.
What fail2ban isn’t
It’s not a firewall replacement. It’s a complement. Fail2ban reacts to patterns in your logs; a firewall blocks by rule. You need both.
It’s not going to stop a determined attacker with a botnet. But it will stop the vast majority of automated brute-force attempts, which is what you’re dealing with 99% of the time.
The bots scanning the internet are looking for easy targets. Make your server slightly harder to attack, and they move on to the next one. That’s all fail2ban needs to do.
Related Posts
Vim survival guide for Linux admins
The minimum vim knowledge you need to survive on a Linux server. Editing config files without wanting to throw your keyboard.
Understanding the Linux filesystem hierarchy
Where things live on a Linux system and why. A practical guide to the directory layout.
Understanding PAM authentication
How Linux authentication works under the hood. PAM modules, configuration files, and common customizations.