Set up Fail2ban with Cloudflare API integration for automatic IP blocking and enhanced security

Intermediate 45 min Apr 30, 2026 97 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure Fail2ban to automatically add malicious IPs to Cloudflare's firewall rules for enhanced protection. This tutorial covers installation, custom filters, API integration, and monitoring for comprehensive security automation across your infrastructure.

Prerequisites

  • Root or sudo access
  • Active Cloudflare account with API access
  • Domain configured with Cloudflare DNS
  • Python 3.6 or later

What this solves

Fail2ban blocks malicious IPs by monitoring log files and creating temporary firewall rules. When integrated with Cloudflare's API, blocked IPs are automatically added to your Cloudflare firewall, protecting all services behind your domain. This creates a distributed security layer that scales across multiple servers and services.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions of security tools.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Fail2ban and dependencies

Install Fail2ban along with Python3 and pip for the Cloudflare API integration script.

sudo apt install -y fail2ban python3 python3-pip curl jq
pip3 install --user requests python-cloudflare
sudo dnf install -y fail2ban python3 python3-pip curl jq
pip3 install --user requests python-cloudflare

Create Cloudflare API credentials

Generate API credentials from your Cloudflare dashboard for zone and firewall management.

sudo mkdir -p /etc/fail2ban/scripts
sudo touch /etc/fail2ban/cloudflare.conf
sudo chmod 600 /etc/fail2ban/cloudflare.conf

Add your Cloudflare credentials to the configuration file:

[cloudflare]
api_token = your_api_token_here
zone_id = your_zone_id_here
email = your_cloudflare_email@example.com
api_key = your_global_api_key_here

Create Cloudflare integration script

This Python script handles adding and removing IPs from Cloudflare firewall rules.

#!/usr/bin/env python3
import sys
import requests
import configparser
import json
import time

config = configparser.ConfigParser()
config.read('/etc/fail2ban/cloudflare.conf')

API_TOKEN = config.get('cloudflare', 'api_token')
ZONE_ID = config.get('cloudflare', 'zone_id')
BASE_URL = 'https://api.cloudflare.com/client/v4'

headers = {
    'Authorization': f'Bearer {API_TOKEN}',
    'Content-Type': 'application/json'
}

def ban_ip(ip_address):
    """Add IP to Cloudflare firewall rules"""
    rule_data = {
        'mode': 'block',
        'configuration': {
            'target': 'ip',
            'value': ip_address
        },
        'notes': f'Fail2ban auto-ban: {ip_address} at {time.strftime("%Y-%m-%d %H:%M:%S")}'
    }
    
    response = requests.post(
        f'{BASE_URL}/zones/{ZONE_ID}/firewall/access_rules/rules',
        headers=headers,
        json=rule_data
    )
    
    if response.status_code == 200:
        print(f'Successfully banned {ip_address} in Cloudflare')
        return True
    else:
        print(f'Failed to ban {ip_address}: {response.text}')
        return False

def unban_ip(ip_address):
    """Remove IP from Cloudflare firewall rules"""
    # First, find the rule ID
    response = requests.get(
        f'{BASE_URL}/zones/{ZONE_ID}/firewall/access_rules/rules',
        headers=headers,
        params={'configuration.target': 'ip', 'configuration.value': ip_address}
    )
    
    if response.status_code == 200:
        rules = response.json().get('result', [])
        for rule in rules:
            rule_id = rule['id']
            delete_response = requests.delete(
                f'{BASE_URL}/zones/{ZONE_ID}/firewall/access_rules/rules/{rule_id}',
                headers=headers
            )
            if delete_response.status_code == 200:
                print(f'Successfully unbanned {ip_address} from Cloudflare')
                return True
    
    print(f'Failed to unban {ip_address} or rule not found')
    return False

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print('Usage: cloudflare-ban.py  ')
        sys.exit(1)
    
    action = sys.argv[1]
    ip_address = sys.argv[2]
    
    if action == 'ban':
        ban_ip(ip_address)
    elif action == 'unban':
        unban_ip(ip_address)
    else:
        print('Invalid action. Use "ban" or "unban"')
        sys.exit(1)

Make the script executable:

sudo chmod +x /etc/fail2ban/scripts/cloudflare-ban.py

Configure Fail2ban main settings

Create the main Fail2ban configuration with enhanced security settings.

[DEFAULT]

Ban settings

bantime = 3600 findtime = 600 maxretry = 3 banaction = iptables-multiport banaction_allports = iptables-allports

Email notifications

destemail = admin@example.com sendername = Fail2ban-Server mta = sendmail

Cloudflare integration

action = %(action_mwl)s cloudflare-ban

Ignore local networks

ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12 [sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 7200 [nginx-http-auth] enabled = true filter = nginx-http-auth logpath = /var/log/nginx/error.log maxretry = 2 [nginx-limit-req] enabled = true filter = nginx-limit-req logpath = /var/log/nginx/error.log maxretry = 10 bantime = 3600 [apache-auth] enabled = false filter = apache-auth logpath = /var/log/apache2/error.log maxretry = 2 [apache-badbots] enabled = false filter = apache-badbots logpath = /var/log/apache2/access.log maxretry = 1 bantime = 86400

Create Cloudflare action configuration

Define the Cloudflare ban action that Fail2ban will execute.

[Definition]

Action to ban IP addresses in Cloudflare

actionstart = actionstop = actioncheck = actionban = /etc/fail2ban/scripts/cloudflare-ban.py ban actionunban = /etc/fail2ban/scripts/cloudflare-ban.py unban [Init]

Default values

name = cloudflare-ban

Create custom filters for enhanced detection

Add custom filters to detect additional attack patterns beyond the default ones.

[Definition]
failregex = ^\s\[error\].limiting requests, excess:. by zone.client: 
ignoreregex =
[Definition]
failregex = ^\s\[error\]. user .* password mismatch, client: 
            ^\s\[error\]. user . was not found in ., client: 
            ^\s\[error\]. access forbidden by rule, client: 
ignoreregex =
[Definition]

Custom filter for applications behind Cloudflare

Looks for real IP in CF-Connecting-IP header

failregex = ^.\[CF-Connecting-IP: \].login failed ^.\[CF-Connecting-IP: \].authentication failed ^.\[CF-Connecting-IP: \].invalid password ignoreregex =

Configure log monitoring and alerting

Set up enhanced logging and email notifications for security events.

[DEFAULT]

Enhanced email notifications

action = %(action_mwl)s cloudflare-ban

Custom notification action

action_mwl = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] %(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]

Slack webhook notification (optional)

[slack-notify] enabled = false filter = logpath = /var/log/fail2ban.log maxretry = 1 bantime = 60 findtime = 60 action = slack[webhook="https://hooks.slack.com/your/webhook/url"]

Enable and start Fail2ban

Start the Fail2ban service and enable it to start automatically on boot.

sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban

Test the Cloudflare integration

Verify that the Cloudflare API integration works correctly.

# Test the ban script
sudo /etc/fail2ban/scripts/cloudflare-ban.py ban 203.0.113.10

Check if the IP was added to Cloudflare

curl -X GET "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/firewall/access_rules/rules" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" | jq '.result[] | select(.configuration.value=="203.0.113.10")'

Test the unban script

sudo /etc/fail2ban/scripts/cloudflare-ban.py unban 203.0.113.10

Advanced configuration options

Configure geo-blocking integration

Add geographical restrictions to complement IP-based blocking.

[Definition]
actionstart = 
actionstop = 
actioncheck = 
actionban = /etc/fail2ban/scripts/cloudflare-geo.py ban 
actionunban = 

[Init]
name = cloudflare-geo

Set up threat intelligence integration

Integrate with threat intelligence feeds for proactive blocking.

#!/usr/bin/env python3
import requests
import json
from datetime import datetime, timedelta

def check_threat_intel(ip_address):
    """Check IP against threat intelligence feeds"""
    # AbuseIPDB integration (requires API key)
    abuseipdb_key = "your_abuseipdb_api_key"
    
    headers = {
        'Key': abuseipdb_key,
        'Accept': 'application/json'
    }
    
    params = {
        'ipAddress': ip_address,
        'maxAgeInDays': 90,
        'verbose': ''
    }
    
    try:
        response = requests.get(
            'https://api.abuseipdb.com/api/v2/check',
            headers=headers,
            params=params
        )
        
        if response.status_code == 200:
            data = response.json()
            confidence = data.get('data', {}).get('abuseConfidencePercentage', 0)
            return confidence > 50  # Ban if confidence > 50%
    except Exception as e:
        print(f"Threat intel check failed: {e}")
    
    return False

if __name__ == '__main__':
    import sys
    if len(sys.argv) > 1:
        ip = sys.argv[1]
        if check_threat_intel(ip):
            print(f"IP {ip} flagged by threat intelligence")
            sys.exit(0)
        else:
            print(f"IP {ip} not flagged")
            sys.exit(1)
sudo chmod +x /etc/fail2ban/scripts/threat-intel.py

Configure monitoring and metrics

Set up monitoring integration for security event tracking. This complements existing monitoring setups like Prometheus and Grafana monitoring.

#!/usr/bin/env python3
import json
import time
from datetime import datetime
import subprocess

def send_metrics(action, ip, jail_name):
    """Send metrics to monitoring system"""
    metric_data = {
        'timestamp': datetime.utcnow().isoformat(),
        'action': action,
        'ip': ip,
        'jail': jail_name,
        'hostname': subprocess.getoutput('hostname')
    }
    
    # Write to log file for collection by log aggregation
    with open('/var/log/fail2ban-metrics.json', 'a') as f:
        f.write(json.dumps(metric_data) + '\n')
    
    # Optional: Send to metrics endpoint
    # requests.post('http://your-metrics-endpoint', json=metric_data)

if __name__ == '__main__':
    import sys
    if len(sys.argv) >= 4:
        send_metrics(sys.argv[1], sys.argv[2], sys.argv[3])
sudo chmod +x /etc/fail2ban/scripts/metrics.py

Verify your setup

Check that Fail2ban is running and monitoring your services correctly.

# Check Fail2ban status
sudo systemctl status fail2ban

List active jails

sudo fail2ban-client status

Check specific jail status

sudo fail2ban-client status sshd

View recent bans

sudo fail2ban-client get sshd bantime sudo fail2ban-client get sshd banip

Test log parsing

sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

Check Cloudflare API connectivity

curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json"

Monitor real-time activity

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

Performance optimization

Fine-tune Fail2ban for better performance on busy servers.

# Add to [DEFAULT] section for high-traffic servers
[DEFAULT]

Reduce log polling frequency for better performance

backend = systemd

Optimize for high log volumes

usedns = no logencoding = auto

Adjust timeouts

bantime.increment = true bantime.rndtime = 300 bantime.maxtime = 86400 bantime.factor = 2
Security note: Always test your Fail2ban configuration in a non-production environment first. Incorrect configuration can lock out legitimate users or administrators. Keep a backup access method available (such as console access) when implementing IP-based blocking.

Common issues

Symptom Cause Fix
Fail2ban won't start Configuration syntax error sudo fail2ban-client -t to test config
Cloudflare API calls fail Invalid API token or permissions Verify token has Zone:Read and Zone:Edit permissions
No bans triggered Log file permissions or path Check ls -la /var/log/auth.log and Fail2ban user permissions
Email notifications not working MTA not configured Install and configure postfix: sudo apt install postfix
High CPU usage Too frequent log polling Increase findtime and use backend = systemd
IPs not unbanning automatically Cloudflare script errors Check /var/log/fail2ban.log for script execution errors
False positive bans Aggressive filters Increase maxretry values and add IPs to ignoreip

Next steps

Running this in production?

Want this handled for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and performant across environments is the harder part. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.