A small SSH brute-force alerter in Python
Scope
The server already runs fail2ban: three failed SSH attempts and the source IP is banned for 24 hours. fail2ban does not report, so attempts are handled without being recorded anywhere visible. This script covers that gap. It reports the source IP, the number of attempts, and the usernames tried.
It does not ban anything. It runs alongside fail2ban and reports to the same Discord webhook as the drive health and monitoring alerts.
How it works
The whole thing is standard library: read auth.log, match failed login lines, aggregate by source IP, and post an alert if any IP crosses a threshold. No dependencies means nothing to install or break when it runs from cron.
The matching and aggregation:
FAILED = re.compile(
r'Failed (?:password|none) for (?:invalid user )?(?P<user>\S+) '
r'from (?P<ip>[0-9a-fA-F.:]+)'
)
def parse(path):
attempts = Counter()
users = defaultdict(set)
with open(path, errors='replace') as log:
for line in log:
m = FAILED.search(line)
if m:
attempts[m['ip']] += 1
users[m['ip']].add(m['user'])
return attempts, users
sshd logs invalid user in a different line format to a real user with a wrong password. Automated scanners overwhelmingly guess non-existent usernames (admin, oracle, test). Capturing both formats and keeping the username set per IP separates untargeted scanning from attempts against a real account.
Alerting is a plain webhook POST, also stdlib:
def alert(ip, count, users):
body = json.dumps({'content':
f'{count} failed SSH attempts from {ip} '
f'(users tried: {", ".join(sorted(users))})'}).encode()
req = urllib.request.Request(
WEBHOOK_URL, data=body,
headers={'Content-Type': 'application/json'})
urllib.request.urlopen(req, timeout=10)
Sample output
Console output is a per-IP breakdown sorted by volume (IPs below are sanitised to documentation ranges):
$ python3 sshwatch.py /var/log/auth.log
203.0.113.47 312 attempts users: admin, oracle, root, test
198.51.100.9 41 attempts users: root
192.0.2.200 3 attempts users: anders
2 IPs over threshold (10): alerts sent
Limitations and next steps
- It reads
auth.logdirectly, which works on Ubuntu but ties it to rsyslog. Reading from journald would make it portable to systems that do not keep the flat file. - Run repeatedly over the same log it will re-alert on the same attacker. The next step is a small state file recording per-IP counts at last run, so alerts only fire on new activity.
- Counts reset when the log rotates, so a slow attacker who stays under the threshold per rotation window goes unnoticed. Tracking across rotated logs would close that.
More write-ups: VLAN discovery and responsible disclosure · Hardened home lab server