← Back to Dashboard

How to Block Malicious IPs

A practical guide to network-level IP blocking for Linux sysadmins, with code examples for every major method

Why Block at the Network Level?

Application-level blocking (in your web app or SSH config) is good, but network-level blocking is better. When you block an IP at the firewall, the attacker's packets are dropped before they reach any application, reducing CPU load, bandwidth consumption, and log noise. For high-volume attacks like DDoS or brute force campaigns, this difference matters enormously.

Network-level blocking also protects all services on the host simultaneously. An IP blocked in iptables cannot reach your SSH, web server, database, or any other listening service. This is especially important for multi-service servers where each application would otherwise need its own blocklist.

The key is to combine reactive blocking (ban after detection) with proactive blocking (ban known threats before they attack). Threat intelligence feeds like WAYSCloud provide the data for proactive blocking, while tools like fail2ban handle reactive blocking.

Method 1: iptables (Classic Linux Firewall)

iptables is available on virtually every Linux system and provides granular control. For blocking malicious IPs, use the INPUT chain to drop inbound traffic and optionally the OUTPUT chain to prevent compromised services from communicating outbound:

# Block a single IP (inbound)
iptables -A INPUT -s 192.168.1.100 -j DROP

# Block a CIDR range
iptables -A INPUT -s 45.92.0.0/16 -j DROP

# Block outbound to a C2 server (prevents data exfiltration)
iptables -A OUTPUT -d 185.220.101.0/24 -j DROP

# Using ipset for large blocklists (MUCH faster than individual rules)
ipset create blocklist hash:ip maxelem 100000 timeout 86400
ipset add blocklist 192.168.1.100
iptables -I INPUT -m set --match-set blocklist src -j DROP

# Bulk import from WAYSCloud API into ipset
curl -s "https://ip.wayscloud.services/api/threats/live?limit=500" \
  | jq -r '.threats[].ip_address' \
  | while read ip; do
      ipset add blocklist "$ip" 2>/dev/null
    done

# Save iptables rules to persist across reboot
iptables-save > /etc/iptables/rules.v4

Performance tip: Never add thousands of individual iptables rules. Use ipset instead. A single ipset match rule can check against 100,000 IPs faster than 100 sequential iptables rules. The lookup is O(1) using hash tables.

Method 2: nftables (Modern Linux Firewall)

nftables is the replacement for iptables on modern Linux distributions (Debian 11+, Ubuntu 21.04+, RHEL 9+). It has a cleaner syntax and native set support without needing ipset:

# Create a table and chain
nft add table ip filter
nft add chain ip filter input { type filter hook input priority 0 \; }

# Create a named set for blocklist IPs
nft add set ip filter blocklist { type ipv4_addr \; flags timeout \; }

# Add IPs to the set (with 24h timeout)
nft add element ip filter blocklist { 192.168.1.100 timeout 24h }
nft add element ip filter blocklist { 45.92.1.0/24 timeout 24h }

# Create a drop rule for the set
nft add rule ip filter input ip saddr @blocklist drop

# Bulk import from WAYSCloud
curl -s "https://ip.wayscloud.services/api/threats/live?limit=500" \
  | jq -r '.threats[].ip_address' \
  | xargs -I {} nft add element ip filter blocklist { {} timeout 24h }

Method 3: fail2ban (Automated Reactive Blocking)

fail2ban watches log files for patterns (failed logins, exploit attempts) and automatically bans offending IPs. It is the standard tool for reactive SSH and web application protection:

# Install
sudo apt install fail2ban   # Debian/Ubuntu
sudo yum install fail2ban   # RHEL/CentOS

# Configure SSH protection (/etc/fail2ban/jail.local)
[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 3
bantime  = 3600      # 1 hour ban
findtime = 600       # Within 10 minutes

# Configure aggressive protection for repeat offenders
[sshd-aggressive]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 1         # Ban on FIRST attempt
bantime  = 86400     # 24 hour ban
findtime = 86400     # Look at full day
banaction = iptables-allports  # Block ALL ports, not just SSH

# Start and enable
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

# Check status
sudo fail2ban-client status sshd
sudo fail2ban-client banned

fail2ban can also be configured to report banned IPs to WAYSCloud, contributing to the shared threat intelligence network. See the integration guide for setup instructions.

Method 4: nginx (Web Server Level)

For web-facing servers, nginx can deny traffic from specific IPs or IP ranges before the request reaches your application:

# nginx - block specific IPs (/etc/nginx/conf.d/blocklist.conf)
# Include this in your server block
deny 192.168.1.100;
deny 45.92.0.0/16;
deny 185.220.101.0/24;
# ... add more as needed

# nginx - use geo module for large blocklists (more efficient)
geo $blocked_ip {
    default 0;
    192.168.1.100 1;
    45.92.0.0/16  1;
    185.220.101.0/24 1;
    # include /etc/nginx/blocklist-ips.conf;  # from file
}

server {
    if ($blocked_ip) {
        return 444;  # Drop connection silently
    }
}

# Generate blocklist from WAYSCloud API
curl -s "https://ip.wayscloud.services/api/threats/live?limit=1000" \
  | jq -r '.threats[].ip_address' \
  | awk '{print "deny " $1 ";"}' \
  > /etc/nginx/conf.d/wayscloud-blocklist.conf

# Reload nginx
sudo nginx -t && sudo nginx -s reload

Method 5: Automated API-Based Blocking with Python

For production environments, automate the entire process with a cron job or daemon that fetches threat data and updates your firewall in real time:

#!/usr/bin/env python3
"""
WAYSCloud Threat Blocker
Fetches live threat data and blocks high-risk IPs via ipset.
Run as cron every 15 minutes: */15 * * * * /usr/local/bin/wayscloud-blocker
"""
import subprocess
import requests
import sys

API_URL = "https://ip.wayscloud.services/api/threats/live"
IPSET_NAME = "wayscloud_blocklist"
MIN_SCORE = 60   # Only block IPs with threat_score >= 60
MAX_IPS = 1000   # Maximum IPs to import per run

def fetch_threats():
    """Fetch high-risk IPs from WAYSCloud"""
    try:
        resp = requests.get(API_URL, params={"limit": MAX_IPS}, timeout=30)
        resp.raise_for_status()
        data = resp.json()
        return [
            t["ip_address"] for t in data.get("threats", [])
            if t.get("threat_score", 0) >= MIN_SCORE
        ]
    except Exception as e:
        print(f"Error fetching threats: {e}", file=sys.stderr)
        return []

def update_ipset(ips):
    """Update ipset with new IPs"""
    # Ensure ipset exists
    subprocess.run(
        ["ipset", "create", IPSET_NAME, "hash:ip",
         "maxelem", "100000", "timeout", "86400"],
        capture_output=True
    )
    for ip in ips:
        subprocess.run(
            ["ipset", "add", IPSET_NAME, ip, "timeout", "86400"],
            capture_output=True
        )
    print(f"Updated {IPSET_NAME}: {len(ips)} IPs added/refreshed")

if __name__ == "__main__":
    ips = fetch_threats()
    if ips:
        update_ipset(ips)

Method 6: Bash One-Liner (Quick and Simple)

For quick ad-hoc blocking when you need to act fast:

# Block top 100 threats right now
curl -s "https://ip.wayscloud.services/api/threats/live?limit=100" \
  | jq -r '.threats[].ip_address' \
  | xargs -I {} iptables -A INPUT -s {} -j DROP

# Check a specific IP before blocking
curl -s "https://ip.wayscloud.services/api/v1/ip/185.220.101.1" | jq '.'

# Block all IPs from a specific country (use with caution)
curl -s "https://ip.wayscloud.services/api/country/RU" \
  | jq -r '.top_ips[].ip_address' \
  | head -50 \
  | xargs -I {} iptables -A INPUT -s {} -j DROP

Best Practices for IP Blocking

Blocking malicious IPs is powerful but should be done carefully to avoid collateral damage:

  • Use threat scores, not just presence — Not every flagged IP deserves a permanent block. WAYSCloud provides a threat score (0-100). Block IPs scoring above 60-80, monitor those scoring 30-60, and allow those below 30. This prevents blocking dynamic residential IPs that may have been temporarily compromised.
  • Set ban expiration times — Use timeouts (24 hours, 7 days) rather than permanent bans. Threat IPs change frequently as attackers rotate infrastructure. Permanent bans accumulate stale entries and waste memory.
  • Maintain a whitelist — Always whitelist your own IPs, monitoring services, CDN IPs (Cloudflare, AWS), payment processors, and other critical services. A false positive blocking Cloudflare will take down your entire website.
  • Log before you drop — Use LOG rules before DROP rules during testing so you can audit what is being blocked. This helps identify false positives quickly.
  • Use ipset or nftables sets — Never add thousands of individual iptables rules. Use set-based matching for O(1) lookups instead of O(n) sequential rule evaluation.
  • Automate updates — Threat data goes stale within hours. Set up a cron job that refreshes your blocklist from the WAYSCloud API every 15-60 minutes for maximum protection.
  • Consider geographic blocking carefully — Blocking entire countries may be appropriate for some services (e.g., a Norwegian company's admin panel), but should never be applied to public-facing content or APIs without careful analysis.

Choosing the Right Method

Method Best For Scale Automation
iptables + ipset All-purpose network blocking 100K+ IPs Cron + API
nftables Modern Linux distributions 100K+ IPs Cron + API
fail2ban Reactive SSH/web protection Per-service Automatic
nginx deny Web server level only 10K IPs Config reload
Python script Custom logic and integration Unlimited Full control

Most production setups benefit from combining methods: fail2ban for reactive protection, plus a cron-based ipset updater fed by WAYSCloud API for proactive threat blocking.

Related Threat Intelligence

Top Malicious IPs Today → What is SSH Brute Force? → What is Botnet C2? → Why Cloud IPs Are Abused → API Integration Guide → ASN Intelligence →