Configuring Fail2ban Security for Linux VPS

Configuring Fail2ban Security for Linux VPS

What You’ll Need

  • A clean Linux server running Ubuntu 22.04 or Debian 12 from Hetzner VPS or Contabo VPS
  • DigitalOcean as an alternative infrastructure provider
  • A domain name registered via Namecheap configured with your server IP address
  • Root or sudo privileges on the server

Table of Contents

Understanding Fail2ban Architecture

Every public Linux virtual private server receives hundreds of automated brute force login attempts every single hour. Malicious bots scan standard IPv4 address ranges to probe port 22 for weak SSH passwords, exploit web application endpoints, and exhaust server resources. Fail2ban acts as an automated intrusion prevention framework that mitigates these attacks dynamically.

Fail2ban operates as a daemon that monitors log files produced by services like SSHD, Nginx, or Postfix. It parses these log streams in real time using Python regular expressions defined in filter configurations. When an IP address accumulates a predefined number of failed authentication attempts within a specific time window, Fail2ban updates firewall rules using iptables, nftables, or UFW to reject or drop network traffic originating from that IP address.

The architecture consists of three main components:

  1. Filters: Regular expression definitions stored in /etc/fail2ban/filter.d/ that match failure events in application logs.
  2. Actions: Execution scripts stored in /etc/fail2ban/action.d/ that alter network firewall tables or trigger external notifications when an IP triggers a rule.
  3. Jails: Configuration blocks inside /etc/fail2ban/jail.local that link a specific filter to an action, defining parameters such as retry limits, observation windows, and ban durations.

Using this modular approach allows you to secure core system services and custom web applications without modifying backend source code.

Step 1: Installing and Initializing Fail2ban

When setting up your Linux instance on a Hetzner VPS or DigitalOcean droplet, you should immediately update the system package database and install Fail2ban along with iptables dependencies.

Execute the following commands to install Fail2ban on Ubuntu or Debian distributions:

sudo apt-get update
sudo apt-get install -y fail2ban iptables python3-systemd

Once installed, check the status of the service using systemctl:

sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo systemctl status fail2ban

The installation package includes default configuration files located in /etc/fail2ban/. The core configuration is contained in /etc/fail2ban/jail.conf. However, you should never edit jail.conf directly because package upgrades will overwrite your changes. Instead, copy jail.conf to create a jail.local override file:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Now open /etc/fail2ban/jail.local in your preferred text editor to modify the global defaults:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.168.1.50
bantime  = 1h
findtime = 10m
maxretry = 5
backend = systemd
banaction = iptables-multiport
banaction_allports = iptables-allports
mta = sendmail
protocol = tcp
chain = INPUT
action = %(action_)s

In this default block, the ignoreip directive ensures your local system or admin IP address never gets locked out. The bantime specifies how long an offending IP remains blocked. Setting findtime = 10m with maxretry = 5 means an IP address will be banned if it commits five failed attempts within a ten minute window. Using backend = systemd enables high performance systemd journal log parsing instead of polling raw file paths.

💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.

Step 2: Hardening SSH with Custom Jail Settings

Securing Secure Shell access is your first priority. SSH brute force bots constantly query servers with common username dictionaries. We will configure an active SSH jail with aggressive ban times for repeated offenders.

Create a dedicated configuration file inside /etc/fail2ban/jail.d/ named sshd.local:

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
findtime = 5m
bantime = 24h
bantime.increment = true
bantime.factor = 2
bantime.formula = ban.time * 1.5 ** ban.count
bantime.maxtime = 8w

This configuration introduces exponential backoff banning through the bantime.increment setting. If a bot attempts to reconnect after its initial 24 hour ban expires, Fail2ban calculates an increased ban time using the formula provided. Repeated attacks will result in long-term isolation lasting up to eight weeks.

To verify your SSH configuration and apply the changes, run:

sudo fail2ban-server --test
sudo fail2ban-client reload

Verify that the SSH jail is running properly:

sudo fail2ban-client status sshd

The output displays active statuses, total banned hosts, and current banned IP lists:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 1
|  |- Total failed:     14
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned: 2
   |- Total banned:     2
   `- Banned IP list:   198.51.100.42 203.0.113.19

If you ever accidentally ban your own remote IP address, you can unban it using the administration CLI:

sudo fail2ban-client unbanip 198.51.100.42

Step 3: Protecting Nginx Web Servers from Attacks

Web applications hosted on Linux VPS instances face frequent malicious scanning, credential stuffing, and path traversal attempts. If you have already read our guide on How to Configure Nginx SSL Subdomain Routing, you know that organizing reverse proxies securely requires structured logging and tight protection rules.

First, let us create a custom filter that detects vulnerability scanners and malicious web bots looking for standard exploit paths like .env files, wp-config.php, or admin interfaces.

Create /etc/fail2ban/filter.d/nginx-probing.conf:

[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD) /.*(\.env|\.git|wp-config\.php|phpmyadmin|admin/config\.php).* HTTP/.*" (403|404|400)
            ^<HOST> -.*"(GET|POST|HEAD) /.*(cgi-bin|vendor/phpunit|setup\.php).* HTTP/.*" (403|404|400)
ignoreregex =

Next, create a second filter to stop HTTP request flooding and rate limit violations in /etc/fail2ban/filter.d/nginx-req-limit.conf:

[Definition]
failregex = ^<HOST> -.*" (GET|POST|HEAD) .* HTTP/.*" (503|429)
ignoreregex =

Now create the corresponding jail definitions in /etc/fail2ban/jail.d/nginx.local:

[nginx-probing]
enabled = true
port = http,https
filter = nginx-probing
logpath = /var/log/nginx/access.log
maxretry = 2
findtime = 10m
bantime = 48h

[nginx-req-limit]
enabled = true
port = http,https
filter = nginx-req-limit
logpath = /var/log/nginx/error.log
maxretry = 10
findtime = 1m
bantime = 1h

[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
findtime = 10m
bantime = 12h

Test your new regular expressions against existing log files before restarting the daemon:

sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-probing.conf

If the syntax matches errors correctly without invalid regex warnings, reload Fail2ban:

sudo fail2ban-client reload

Step 4: Building Custom Telegram Security Notifications

Monitoring system events manually is impractical. We can instruct Fail2ban to send real-time alerts to a Telegram group whenever an IP is banned. If you are building automated event notifications, check out our tutorial on Building Interactive Telegram Bots with Python Asyncio to learn how custom notification pipelines handle incoming hooks.

To set up native Fail2ban alerting via Telegram, create a custom action configuration at /etc/fail2ban/action.d/telegram.conf:

[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl -s -X POST "https://api.telegram.org/bot<telegram_token>/sendMessage" \
            -d "chat_id=<telegram_chat_id>" \
            -d "parse_mode=Markdown" \
            -d "text=🚨 *Fail2ban Alert* %0A%0A*Jail:* `<name>` %0A*Action:* Blocked IP %0A*IP Address:* `<ip>` %0A*Failures:* <failures> attempts"

actionunban = curl -s -X POST "https://api.telegram.org/bot<telegram_token>/sendMessage" \
              -d "chat_id=<telegram_chat_id>" \
              -d "parse_mode=Markdown" \
              -d "text=ℹ️ *Fail2ban Notice* %0A%0A*Jail:* `<name>` %0A*Action:* Unblocked IP %0A*IP Address:* `<ip>`"

[Init]
telegram_token = YOUR_TELEGRAM_BOT_TOKEN_HERE
telegram_chat_id = YOUR_TELEGRAM_CHAT_ID_HERE

Replace YOUR_TELEGRAM_BOT_TOKEN_HERE and YOUR_TELEGRAM_CHAT_ID_HERE with your actual BotFather token and chat identifier.

Now, attach this action to your SSH jail inside /etc/fail2ban/jail.d/sshd.local:

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
findtime = 5m
bantime = 24h
action = %(action_)s
         telegram[telegram_token="123456789:ABCdefGHIjklMNOpqrsTUVwxyZ", telegram_chat_id="-100123456789"]

Once reloaded, any security event detected by Fail2ban triggers an immediate API request to Telegram. For high volume servers generating extensive access data, you can aggregate these incident logs into downstream AI analytics platforms, similar to techniques shown in our guide on Processing Customer Feedback With Claude Function Calling.

Reload Fail2ban to activate live alerts:

sudo fail2ban-client reload

Verify that all active jails are functioning properly across system protocols:

sudo fail2ban-client status

This completes a production-grade Fail2ban implementation on your virtual private host. Your SSH interfaces, custom HTTP endpoints, and system loggers are now secured by automated, exponential firewall bans and instant mobile notifications.

Getting Started

To implement this security setup on your own server infrastructure, select a cloud host, deploy a fresh OS instance, and set up your initial firewall rules.

Outsource Your Automation

Don’t have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr, mention SYS3-HUGO for priority. Or DM at chasebot.online.

Want to automate this yourself?

Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.

Want this engine running on your own VPS?

This blog publishes itself — daily, unattended, on free API tiers. The full engine, Hugo theme, and setup guide are available as System 3.

Get System 3
system online