Sandbox99 Chronicles

Linux Firewall: Automatically Ban and Unban IP Addresses with Python

ban-ip-address

Written by Jose Mendez

Hi, I’m Jose Mendez, the creator of sandbox99.cc. with a passion for technology and a hands-on approach to learning, I’ve spent more than fifteen years navigating the ever-evolving world of IT.

Published May 8, 2025 | Last updated on May 10, 2025 at 5:33PM

Reading Time: 7 minutes

In the world of cybersecurity, quick action can make all the difference.

I’ve just published a new blog post where I share a practical Python script that temporarily bans and unbans IP addresses on a Linux system. It uses UFW as the primary firewall and falls back to iptables if needed — perfect for blocking suspicious traffic for a defined duration.

🔧 Whether you’re a system admin, DevOps engineer, or security enthusiast, this post will help you:

  • Strengthen your server defenses
  • Automate repetitive firewall tasks
  • Improve your incident response workflow

Introduction

In today’s cybersecurity landscape, quickly responding to suspicious or malicious activity is essential for maintaining the integrity of your systems. One practical and widely used method is to temporarily block offending IP addresses — a technique often employed in automated security systems, intrusion detection setups, or manual incident response.

This blog post walks through a real-world solution to that problem. We’ll explore how to automatically ban and unban an IP address on a Linux system using a Python script. The script leverages UFW (Uncomplicated Firewall) as the primary tool, with a fallback to iptables when UFW isn’t available. This automation not only improves response time but also helps minimize manual intervention in routine security operations.

Whether you’re managing a personal server or administering a production environment, this approach is a simple yet effective addition to your security toolkit.

The Python Script: Ban IP Address

I’ll create a Python script that bans a target IP address using UFW as primary (Uncomplicated Firewall) or iptables as fallback for a specified duration and then automatically unbans it. This is a common cybersecurity practice to temporarily block suspicious traffic.

Here’s a script that will:

  1. Accept an IP address and ban duration as arguments
  2. Use UFW (Primary) to block the IP and iptables as fallback
  3. Schedule the unban after the specified duration
#!/usr/bin/env python3
"""
Description: IP Ban Script - Temporarily bans an IP address using UFW (preferred) or iptables
Author: Jose Mendez
Blog site: sandbox99.cc
Last Update: 05/08/2025
Usage: sudo ip_ban_script.py <ip_address> <duration_hours>
"""

import sys
import os
import re
import time
import subprocess
import argparse
import logging
import shutil
from datetime import datetime, timedelta

# Set up logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(),
        logging.FileHandler("/var/log/ip_ban.log")
    ]
)

def validate_ip(ip):
    """Validate that the string is a valid IPv4 address."""
    ipv4_pattern = re.compile(r'^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$')
    if not ipv4_pattern.match(ip):
        return False
    
    # Check that each octet is between 0 and 255
    octets = ip.split('.')
    for octet in octets:
        if int(octet) < 0 or int(octet) > 255:
            return False
    
    return True

def check_root_privileges():
    """Check if script is run with root privileges."""
    if os.geteuid() != 0:
        logging.error("This script requires root privileges. Please run with sudo.")
        return False
    return True

def is_command_available(command):
    """Check if a command is available in the system."""
    return shutil.which(command) is not None

def get_firewall_type():
    """Determine which firewall to use (UFW or iptables)."""
    if is_command_available("ufw"):
        try:
            result = subprocess.run(['ufw', 'status'], 
                                  capture_output=True, 
                                  text=True)
            if "Status: active" in result.stdout:
                return "ufw"
            else:
                logging.warning("UFW is installed but not active. Will check for iptables.")
        except subprocess.CalledProcessError:
            logging.warning("Error checking UFW status. Will check for iptables.")
    
    if is_command_available("iptables"):
        return "iptables"
    
    logging.error("Neither UFW nor iptables is available on this system.")
    return None

# UFW Functions
def ufw_ban_ip(ip_address):
    """Ban an IP address using UFW."""
    try:
        subprocess.run(['ufw', 'deny', 'from', ip_address], 
                      check=True)
        logging.info(f"[UFW] IP {ip_address} has been banned")
        return True
    except subprocess.CalledProcessError as e:
        logging.error(f"[UFW] Failed to ban IP {ip_address}: {e}")
        return False

def ufw_unban_ip(ip_address):
    """Unban an IP address using UFW."""
    try:
        subprocess.run(['ufw', 'delete', 'deny', 'from', ip_address], 
                      check=True)
        logging.info(f"[UFW] IP {ip_address} has been unbanned")
        return True
    except subprocess.CalledProcessError as e:
        logging.error(f"[UFW] Failed to unban IP {ip_address}: {e}")
        return False

# iptables Functions
def iptables_ban_ip(ip_address):
    """Ban an IP address using iptables."""
    try:
        # Check if rule already exists
        check_cmd = ['iptables', '-C', 'INPUT', '-s', ip_address, '-j', 'DROP']
        check_result = subprocess.run(check_cmd, capture_output=True)
        
        if check_result.returncode != 0:  # Rule doesn't exist
            cmd = ['iptables', '-A', 'INPUT', '-s', ip_address, '-j', 'DROP']
            subprocess.run(cmd, check=True)
            logging.info(f"[iptables] IP {ip_address} has been banned")
            return True
        else:
            logging.info(f"[iptables] IP {ip_address} is already banned")
            return True
    except subprocess.CalledProcessError as e:
        logging.error(f"[iptables] Failed to ban IP {ip_address}: {e}")
        return False

def iptables_unban_ip(ip_address):
    """Unban an IP address using iptables."""
    try:
        cmd = ['iptables', '-D', 'INPUT', '-s', ip_address, '-j', 'DROP']
        subprocess.run(cmd, check=True)
        logging.info(f"[iptables] IP {ip_address} has been unbanned")
        return True
    except subprocess.CalledProcessError as e:
        logging.error(f"[iptables] Failed to unban IP {ip_address}: {e}")
        return False

# Generic functions that use the appropriate firewall
def ban_ip(ip_address, firewall_type):
    """Ban an IP address using the specified firewall."""
    if firewall_type == "ufw":
        return ufw_ban_ip(ip_address)
    elif firewall_type == "iptables":
        return iptables_ban_ip(ip_address)
    else:
        logging.error("No supported firewall available")
        return False

def unban_ip(ip_address, firewall_type):
    """Unban an IP address using the specified firewall."""
    if firewall_type == "ufw":
        return ufw_unban_ip(ip_address)
    elif firewall_type == "iptables":
        return iptables_unban_ip(ip_address)
    else:
        logging.error("No supported firewall available")
        return False

def main():
    # Parse command line arguments
    parser = argparse.ArgumentParser(description='Temporarily ban an IP address using UFW or iptables')
    parser.add_argument('ip_address', help='The IPv4 address to ban')
    parser.add_argument('duration', type=float, help='Ban duration in hours')
    args = parser.parse_args()
    
    ip_address = args.ip_address
    duration_hours = args.duration
    
    # Validate input
    if not validate_ip(ip_address):
        logging.error(f"Invalid IPv4 address: {ip_address}")
        sys.exit(1)
    
    if duration_hours <= 0:
        logging.error("Duration must be greater than 0")
        sys.exit(1)
    
    # Check for root privileges
    if not check_root_privileges():
        sys.exit(1)
    
    # Determine which firewall to use
    firewall_type = get_firewall_type()
    if not firewall_type:
        sys.exit(1)
    
    logging.info(f"Using {firewall_type} to ban IP address")
    
    # Ban the IP address
    if not ban_ip(ip_address, firewall_type):
        sys.exit(1)
    
    # Calculate unban time
    ban_time = datetime.now()
    unban_time = ban_time + timedelta(hours=duration_hours)
    duration_seconds = duration_hours * 3600
    
    logging.info(f"IP {ip_address} will be unbanned at {unban_time.strftime('%Y-%m-%d %H:%M:%S')}")
    
    try:
        # Sleep until it's time to unban
        time.sleep(duration_seconds)
        
        # Unban the IP address
        unban_ip(ip_address, firewall_type)
    except KeyboardInterrupt:
        logging.info("Process interrupted. IP remains banned.")
        
        # Provide instructions for manual unban
        if firewall_type == "ufw":
            logging.info(f"To manually unban, run: sudo ufw delete deny from {ip_address}")
        else:  # iptables
            logging.info(f"To manually unban, run: sudo iptables -D INPUT -s {ip_address} -j DROP")

if __name__ == "__main__":
    main()

Key Features:

  1. Validates the IP address format
  2. Requires root privileges either for UFW or iptables
  3. Automatic Firewall Detection: The script first checks for UFW (preferred), and if not available or not active, falls back to iptables.
  4. Separate Implementation: Functions for both UFW and iptables are separated for clarity and maintainability.
  5. Better Error Handling: Checks if a rule already exists for iptables to avoid errors.
  6. Improved Logging: All logs clearly indicate which firewall system is being used (UFW or iptables).
  7. Better System Checks: Uses shutil.which() to check for command availability in the system.
  8. Automatically unbans the IP after the specified duration
  9. Handles interruptions gracefully

Usage

sudo python3 ip_ban_script.py <ip_address> <duration_hours>
# For example, to ban 123.45.67.89 for 2 hours:
sudo python3 ip_ban_script.py 123.45.67.89 2
  • This Will:
    • Check if UFW is available and active
    • If not, fall back to iptables
    • Ban the IP for 2 hours
    • Automatically unban after the duration
  • How Firewall Selection Works:
    • First checks if UFW is installed and active
    • If UFW isn’t available or active, tries iptables
    • If neither is available, exits with an error

Manual Unbanning:

If the script is interrupted, it provides the correct command to manually unban based on which firewall was used:

  • For UFW: sudo ufw delete deny from 203.0.113.5
  • For iptables: sudo iptables -D INPUT -s 203.0.113.5 -j DROP

This approach makes the script much more versatile across different Linux distributions, some of which may use UFW by default (like Ubuntu) and others that might use iptables directly.

Bonus: Pro Tip

The perfect approach for running the script in the background so it continues running even after the administrator logs out or closes the terminal. Let’s break down what your command does:

nohup sudo ip_ban_script.py 1.2.3.4 2 &
  • nohup: This prevents the command from being terminated when you log out
  • sudo: Runs the command with administrator privileges (required for firewall operations)
  • ip-ban 1.2.3.4 2: The script banning IP 1.2.3.4 for 2 hours
  • &: Runs the process in the background immediately

Checking the Running Process

After running the command and returning to the system later, you can indeed use top or htop to check if the process is still running. Here’s how to find it specifically:

  1. Using ps with grep: ps aux | grep ip-ban
  2. Using top with filtering: top -c -p $(pgrep -f ip_ban_script.py)
  3. Using htop with filtering (after launching htop):
    • Press F4 (for search)
    • Type ip-ban
    • The process will be highlighted if it’s running

Additional Notes:

  1. The command will create a nohup.out file in the directory where you ran the command, which contains any output from the script.
  2. You can also check the log file we specified in the script: tail -f /var/log/ip_ban.log
  3. If you need to kill the process before the duration expires: sudo pkill -f ip-ban
    • This will leave the IP banned until the scheduled unban time.
    • You’d need to manually unban as per the instructions in the log file

This approach is ideal for system administrators who need to ban IPs temporarily without having to keep a terminal open for the duration. The script will continue running in the background and automatically unban the IP after the specified duration.

Where this script should be kept?

Best Location Options:

  1. /usr/local/bin/ – This is the ideal location for custom system scripts that should be available to all users. Files placed here are in the default PATH, which means you can run them from anywhere without specifying the full path.
  2. /usr/local/sbin/ – Similar to /usr/local/bin/, but specifically meant for system administration scripts that only administrators would use (which fits your use case well since the script requires root privileges).
  3. /opt/{application-name}/bin/ – If you’re creating a collection of related security scripts, you might create a directory like /opt/security-tools/bin/ and place it there.
  4. /home/{user}/bin/ – For personal use if you’re the only one who will run it. Many distributions automatically add ~/bin to your PATH if it exists.

Recommendation:

I recommend placing the script in /usr/local/sbin/ since:

  1. It’s a system administration tool that requires root privileges
  2. It’s in the standard PATH for administrators
  3. It’s the conventional location for locally-installed administration scripts
  4. by default in Debian 12 this PATH was not included

After placing it there, make it executable:

sudo cp ip_ban_script.py /usr/local/sbin/
sudo chmod +x /usr/local/sbin/ip_ban_script.py

For location 2,3 and 4, Add the directory to your PATH

You have several options for adding the directory to your PATH:

Option 1: For the current user only (recommended for personal use)

Edit your ~/.bashrc file (or ~/.zshrc if you use zsh):

# sample location /usr/local/sbin
echo 'export PATH=$PATH:/usr/local/sbin' >> ~/.bashrc
source ~/.bashrc

Option 2: For all users on the system (recommended for system-wide tools)

Create a new file in /etc/profile.d/:

# sample location /usr/local/sbin
sudo bash -c 'echo "export PATH=\$PATH:/usr/local/sbin" > /etc/profile.d/security-tools.sh'
sudo chmod +x /etc/profile.d/security-tools.sh

Option 3: Using /etc/environment (system-wide but simpler)

Edit the /etc/environment file:

# sample location /usr/local/sbin
sudo bash -c 'echo "PATH=\"/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/local/sbin\"" > /etc/environment'

Verify the PATH is updated

After adding the directory and reloading your shell (or logging out and back in), check that the PATH includes your new directory:

echo $PATH

You should see /usr/local/sbin in the output.

Test the script

Try running the script from any directory:

sudo ip_ban_script.py --help

Important notes:

  1. For Options 2 and 3 (system-wide changes), users will need to log out and log back in for the changes to take effect.
  2. If you’re using Option 3 (/etc/environment), be careful to include ALL existing paths plus your new one, as this will replace the entire PATH variable rather than append to it.
  3. For system administration tools like this, placing them in /opt/security-tools/bin/ with proper PATH configuration makes more sense than /usr/local/bin/ if you’re organizing a suite of security tools.

The most recommended approach for a system tool like this would be Option 2 (using /etc/profile.d/), as it’s the cleanest and safest way to add to the system PATH without modifying existing system files directly.

Final Thoughts

Temporarily banning IP addresses is a straightforward yet powerful way to reduce potential threats and limit malicious access attempts. By automating this process with Python, you can respond swiftly to suspicious activity and ensure that bans are lifted after a set duration — avoiding unnecessary disruptions.

This script is meant to serve as a foundation. You can expand it further by integrating it with log monitoring tools like Fail2ban, a SIEM platform, or even a custom web dashboard. Security is never one-size-fits-all, but having control and visibility over how your system reacts to unwanted traffic is a critical first step.

As always, make sure your automation scripts are well-tested and run with appropriate permissions. Firewalls are powerful — use them wisely.

Related Post

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.