DEV Community

Cover image for That SSH bot rotating IPs in your logs? Ban the whole subnet, then let the ban expire
Phaedon V
Phaedon V

Posted on

That SSH bot rotating IPs in your logs? Ban the whole subnet, then let the ban expire

It started with an SSH alert I've seen a thousand times:

sshd: Invalid user taow from 203.0.113.239 port 50612
Enter fullscreen mode Exit fullscreen mode

Then again. And again. Dozens of times, from a different IP almost every time, but always inside the same /24.

Was it dangerous? No. That server only accepts SSH keys, and I double-checked from another box:

$ ssh user@server
user@server: Permission denied (publickey).
Enter fullscreen mode Exit fullscreen mode

No password prompt, nothing to guess. The bot was trying usernames against a door that doesn't have a keyhole.

But it was still noise: alerts piling up in the SIEM, auth logs filling up, and CPU spent rejecting the same junk over and over. When the real alert eventually arrives, I want to see it, not dig for it under 500 copies of Invalid user oracle.

Why the usual quick fixes didn't feel right

Banning single IPs is pointless against a bot that rotates through a whole range.

ufw deny from 203.0.113.0/24 looks right but often does nothing. UFW appends the rule after your existing allow 22, so the allow matches first and the deny never fires. You need ufw insert 1 ..., and you have to remember that every time.

A raw iptables -I rule works, but then it lives there forever. Six months later, someone finds a mystery DROP rule with no comment, no date and no owner, and nobody dares delete it.

What I actually wanted:

  • ban a whole subnet, not an IP
  • have it expire by itself (most of these bots move on after a day or two)
  • optionally make it permanent when a range keeps coming back
  • not touch the existing firewall setup (UFW, firewalld, whatever's there)
  • leave a readable trail for the next admin

So I wrote a small tool for it: netban.

GitHub logo phaedonv / netban

netban is a single Bash script for blocking whole networks (for example a /24) on a Linux host, either for a fixed time or permanently.

netban — subnet IP range ban

Manual IPv4 network bans for Linux, built on nftables.
Temporary bans that expire on their own. Permanent bans that survive reboots

Bash nftables License: MIT


netban is a single Bash script for blocking whole networks (for example a /24) on a Linux host, either for a fixed time or permanently. It's meant as a manual fallback next to automated defences such as Wazuh active response, Suricata, fail2ban or CrowdSec: you use it when those miss something or when you need to stop traffic right now without editing firewall configs.

$ netban 203.0.113.57/24
banned 203.0.113.0/24 for 24h

Why

A typical case: a server exposes SSH with key-only auth, and a bot keeps hammering it from a rotating pool of IPs inside one subnet:

sshd: Invalid user taow from 203.0.113.239 port 50612
sshd: Invalid user admin from 203.0.113.12 port 41877
sshd: Invalid user oracle from 203.0.113.190 port 39020

It isn't a real threat, because passwords…

What it looks like

$ netban 203.0.113.239/24
banned 203.0.113.0/24 for 24h
Enter fullscreen mode Exit fullscreen mode

You can paste the IP straight from the alert with /24 on the end. The host bits get masked, so it bans the network.

Need a different duration, or a permanent ban with a note for future you?

netban 203.0.113.0/24 6h
netban 198.51.100.0/24 perm "returns daily, ssh user enum"
Enter fullscreen mode Exit fullscreen mode

Or run it with no arguments for an interactive prompt, which is handy when you're triaging a few ranges at once:

$ netban
netban> 203.0.113.0/24
banned 203.0.113.0/24 for 24h
netban> 198.51.100.7/24 2d
banned 198.51.100.0/24 for 2d
netban> list
netban> quit
Enter fullscreen mode Exit fullscreen mode

And to check it's actually doing something:

$ netban -l
--- temporary ---
    elements = { 203.0.113.0/24 timeout 1d expires 23h41m12s }
--- permanent (live) ---
    elements = { 198.51.100.0/24 }
--- permanent (/etc/netban/permanent.list) ---
198.51.100.0/24  # added 2026-10-01 - returns daily, ssh user enum
--- drops ---
@perm counter packets 1843 bytes 110580
@temp counter packets 212 bytes 12720
Enter fullscreen mode Exit fullscreen mode

The counters climb, the log goes quiet, and the SIEM stops shouting.

How it works (it's mostly nftables doing the work)

The whole trick is that nftables already supports sets with per-element timeouts. netban just wraps that in something you can type at 2am without looking anything up.

table inet netban
 ├─ set perm   (interval)            permanent networks
 ├─ set temp   (interval, timeout)   temporary networks, auto-expire
 └─ chain input  hook input priority -10
      ip saddr @perm counter drop
      ip saddr @temp counter drop
Enter fullscreen mode Exit fullscreen mode

A few design choices that matter:

Its own table. netban never edits UFW, firewalld or /etc/nftables.conf. Everything lives in inet netban, so removing it is one command and can't break anything else.

Priority -10. UFW and firewalld hook input at priority 0. netban's chain runs before them, so a banned range is dropped no matter what allow rules sit further down. That fixes the "my deny never matched" problem.

The kernel handles expiry. A temporary ban is just a set element with timeout 24h. There's no cron job, no cleanup script and no state to forget about. When the timer runs out, it's gone.

Permanent bans are plain text. They go into /etc/netban/permanent.list with a date and your note, and a small systemd oneshot (netban.service) reloads them at boot. It's also PartOf=nftables.service, so if someone restarts nftables and flushes the ruleset, the permanent bans come straight back.

The core of a temporary ban is really just this:

nft add element inet netban temp '{ 203.0.113.0/24 timeout 24h }'
Enter fullscreen mode Exit fullscreen mode

Everything else is input validation, CIDR masking, and making it pleasant to use.

Install

It's a single Bash script. You need Linux with nft, Bash 4+ and sudo.

curl -fsSLO https://raw.githubusercontent.com/phaedonv/netban/main/netban
sudo bash netban --install
Enter fullscreen mode Exit fullscreen mode

That puts netban in /usr/local/bin, creates /etc/netban/permanent.list, and enables the boot service. Re-running --install updates in place.

What it is not

I want to be upfront about this, because it's a small tool and I'd rather you know its limits:

  • It's not automatic. It doesn't watch logs. For automatic per-IP banning, use fail2ban, CrowdSec or Wazuh active response. netban is the manual fallback for when those miss something or you need to act right now.
  • It's not real protection on its own. The actual SSH defence is key-only auth (PasswordAuthentication no) and limiting who can reach port 22. netban reduces noise and load.
  • It's IPv4 only. For now.
  • It's host-level. If you control the edge router or cloud firewall, blocking there saves bandwidth for every machine behind it.

Where it fits in a typical layered defence:

Layer Scope When
SIEM / IDS / fail2ban / CrowdSec automatic, per IP first line, reacts to alerts
Geo blocking (optional) automatic, per country broad rotation across many ranges
netban manual, per network, temp or perm fallback when the above miss it

Try it, break it, tell me

netban is MIT-licensed and lives here: github.com/phaedonv/netban. It was written at Catalink as part of a growing set of small blue-team tools we use day to day.

If you try it, I'd love to hear:

  • How do you handle subnet-rotating bots today? fail2ban with a custom action, CrowdSec, edge firewall, or just ignore them?
  • IPv6 support: worth adding, or do you mostly see this noise over v4?
  • Anything that broke on your distro. Issues and PRs are very welcome.

If it saved you a few minutes of log-scrolling, a ⭐ on the repo helps other admins find it.


The tool and this post were written with help from an AI assistant. The problem, the servers and the decisions are mine.

Top comments (0)