- Posted on
- • Artificial Intelligence
Artificial Intelligence SSH Hardening
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence SSH Hardening: Let Your Logs Fight Back
If you expose SSH to the internet, you’re under attack—right now. Botnets pound port 22 with password sprays, credential stuffing, and slow-burn brute force. Static rules help, but attackers evolve. The good news: you already collect the data you need to fight back, and with a bit of automation and “AI-ish” behavior analysis, you can turn your logs into a living defensive layer.
This article shows you how to harden SSH with a modern, layered approach:
Solid baseline config (keys, MFA-ready, sane limits)
Automatic bans with Fail2Ban
Collaborative, behavior-aware blocking with CrowdSec
A tiny local anomaly detector using Python and Isolation Forest
Fast visibility into attacks
You’ll get copy-paste commands for apt, dnf, and zypper throughout.
Note: Always keep a second shell open and verified before restarting SSH to avoid locking yourself out.
Why AI-assisted SSH hardening is worth it
Attack patterns don’t stand still. Fixed rules miss novel or slow/low behaviors.
Logs are gold. SSH emits rich signals (auth failures, usernames, IPs, timing) perfect for anomaly detection.
Automation buys time. Let the system auto-ban obvious abusers so you can focus on outliers.
It’s additive. You’re not replacing keys or firewalling; you’re adding a responsive layer on top.
1) Baseline SSH hardening (keys, policy, and timeouts)
Install OpenSSH server if you haven’t:
- Debian/Ubuntu (apt):
sudo apt update && sudo apt install -y openssh-server
sudo systemctl enable --now ssh
- Fedora/RHEL (dnf):
sudo dnf install -y openssh-server
sudo systemctl enable --now sshd
- openSUSE (zypper):
sudo zypper refresh && sudo zypper install -y openssh
sudo systemctl enable --now sshd
Edit your sshd_config (commonly /etc/ssh/sshd_config):
# Key-only auth (recommended)
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
# Strong defaults
PermitRootLogin no
MaxAuthTries 3
LoginGraceTime 20
ClientAliveInterval 300
ClientAliveCountMax 2
UseDNS no
AllowTcpForwarding no
# Optional: lock down who can log in
# AllowUsers alice@198.51.100.0/24 bob
# If you must keep passwords for a while, rate-limit and plan to disable ASAP.
Restart SSH:
- Debian/Ubuntu:
sudo systemctl restart ssh
- Fedora/openSUSE:
sudo systemctl restart sshd
Tip: Hardware-backed keys are easy and strong. If you have a FIDO2/U2F key:
ssh-keygen -t ed25519-sk -C "you@host"
Then add the public key to ~/.ssh/authorized_keys on the server.
2) Add auto-banning with Fail2Ban
Fail2Ban reads logs and bans IPs via firewall when patterns are tripped. It’s simple, reliable, and a great first layer.
Install:
- Debian/Ubuntu (apt):
sudo apt install -y fail2ban
- Fedora/RHEL (dnf):
sudo dnf install -y fail2ban
- openSUSE (zypper):
sudo zypper install -y fail2ban
Enable and start:
sudo systemctl enable --now fail2ban
Minimal jail for SSH: create /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1h
ignoreip = 127.0.0.1/8 ::1 192.0.2.0/24
Reload:
sudo systemctl reload fail2ban
Check bans:
sudo fail2ban-client status sshd
3) Behavior-aware blocking with CrowdSec
CrowdSec enriches your defenses with community-driven, behavior-based detection. It watches your logs, identifies attack patterns, and can automatically add firewall rules. You can also opt-in to a community blocklist to benefit from others’ sightings.
Add the official repository and install:
- Debian/Ubuntu (apt):
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
sudo apt install -y crowdsec crowdsec-firewall-bouncer-iptables
# or, if you use nftables:
# sudo apt install -y crowdsec-firewall-bouncer-nftables
- Fedora/RHEL (dnf):
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.rpm.sh | sudo bash
sudo dnf install -y crowdsec crowdsec-firewall-bouncer-iptables
# or:
# sudo dnf install -y crowdsec-firewall-bouncer-nftables
- openSUSE (zypper):
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.rpm.sh | sudo bash
sudo zypper install -y crowdsec crowdsec-firewall-bouncer-iptables
# or:
# sudo zypper install -y crowdsec-firewall-bouncer-nftables
Enable services:
sudo systemctl enable --now crowdsec
sudo systemctl enable --now crowdsec-firewall-bouncer
Make sure CrowdSec reads your SSH logs. Create /etc/crowdsec/acquis.d/sshd.yaml:
filenames:
- /var/log/auth.log # Debian/Ubuntu
- /var/log/secure # RHEL/Fedora
labels:
type: syslog
Install the SSH detection collection and reload:
sudo cscli collections install crowdsecurity/sshd
sudo systemctl restart crowdsec
Verify decisions:
sudo cscli decisions list
sudo cscli metrics
Test by generating a few failed SSH logins from a test IP and watch CrowdSec react.
4) A tiny local “AI” anomaly detector that cooperates with Fail2Ban
Beyond static regex rules, you can flag unusual behavior (e.g., an IP that fails across many users very quickly). Below is a minimal Python script that:
Parses auth logs
Builds simple features per IP
Uses Isolation Forest to score anomalies
Instructs Fail2Ban to ban the worst offenders
Install Python and libraries:
- Debian/Ubuntu (apt):
sudo apt install -y python3 python3-pip
python3 -m pip install --user scikit-learn pandas
- Fedora/RHEL (dnf):
sudo dnf install -y python3 python3-pip
python3 -m pip install --user scikit-learn pandas
- openSUSE (zypper):
sudo zypper install -y python3 python3-pip
python3 -m pip install --user scikit-learn pandas
Create /usr/local/sbin/ssh_ai_anomaly.py and make it executable:
sudo install -m 0755 /dev/stdin /usr/local/sbin/ssh_ai_anomaly.py <<'PY'
#!/usr/bin/env python3
import os, re, subprocess, sys
from datetime import datetime, timedelta
from collections import defaultdict
import pandas as pd
from sklearn.ensemble import IsolationForest
# Adjust for your distro
CANDIDATE_LOGS = ["/var/log/auth.log", "/var/log/secure"]
ALLOWLIST = set(os.environ.get("SSH_AI_ALLOWLIST", "127.0.0.1,::1").split(","))
def find_log():
for p in CANDIDATE_LOGS:
if os.path.exists(p):
return p
print("No known auth log found.", file=sys.stderr)
sys.exit(1)
logfile = find_log()
cutoff = datetime.utcnow() - timedelta(minutes=15)
# e.g., "Failed password for invalid user user from 203.0.113.5 port 5555 ssh2"
re_fail = re.compile(r'Failed password .* from ([0-9a-fA-F:.]+) ')
re_time = re.compile(r'^([A-Z][a-z]{2}\s+\d+\s+\d+:\d+:\d+)') # "Jan 2 03:04:05"
# Accumulate per-IP stats
stats = defaultdict(lambda: {"fails":0, "users":set(), "first":None, "last":None})
with open(logfile, "r", errors="ignore") as f:
for line in f:
if "sshd" not in line or "Failed password" not in line:
continue
m = re_fail.search(line)
if not m:
continue
# Parse time in current year (approximate but sufficient for short window)
tm = re_time.match(line)
if not tm:
continue
t = datetime.strptime(f"{datetime.utcnow().year} {tm.group(1)}", "%Y %b %d %H:%M:%S")
if t < cutoff:
continue
ip = m.group(1)
if ip in ALLOWLIST:
continue
# Guess username field
user_m = re.search(r'Failed password for (?:invalid user )?(\S+)', line)
user = user_m.group(1) if user_m else "unknown"
s = stats[ip]
s["fails"] += 1
s["users"].add(user)
s["first"] = min(s["first"], t) if s["first"] else t
s["last"] = max(s["last"], t) if s["last"] else t
if not stats:
sys.exit(0)
# Build a small feature frame
rows = []
for ip, s in stats.items():
duration = max(1, int((s["last"] - s["first"]).total_seconds()))
rate = s["fails"] / duration # fails per second
rows.append({
"ip": ip,
"fails": s["fails"],
"unique_users": len(s["users"]),
"duration_s": duration,
"rate": rate
})
df = pd.DataFrame(rows)
# Isolation Forest for outlier scoring
model = IsolationForest(n_estimators=100, contamination=0.1, random_state=42)
features = df[["fails", "unique_users", "duration_s", "rate"]]
model.fit(features)
scores = model.score_samples(features) # higher = less anomalous
df["anomaly"] = -scores # higher = more anomalous
# Pick obvious offenders (tune threshold as needed)
suspicious = df[(df["fails"] >= 5) & (df["anomaly"] > 0.5)]
for ip in suspicious["ip"]:
# Hand off to Fail2Ban so bans are tracked centrally
try:
subprocess.run(["fail2ban-client", "set", "sshd", "banip", ip], check=True)
print(f"Banned {ip} via Fail2Ban (AI anomaly)")
except Exception as e:
print(f"Failed to ban {ip}: {e}", file=sys.stderr)
PY
Optional: run it on a timer. Create /etc/systemd/system/ssh-ai-bouncer.service:
[Unit]
Description=SSH AI anomaly bouncer
After=network.target
[Service]
Type=oneshot
Environment=SSH_AI_ALLOWLIST=127.0.0.1,::1,192.0.2.0/24
ExecStart=/usr/bin/env python3 /usr/local/sbin/ssh_ai_anomaly.py
Create /etc/systemd/system/ssh-ai-bouncer.timer:
[Unit]
Description=Run SSH AI anomaly bouncer every 5 minutes
[Timer]
OnBootSec=2m
OnUnitActiveSec=5m
Unit=ssh-ai-bouncer.service
[Install]
WantedBy=timers.target
Enable the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now ssh-ai-bouncer.timer
Safety tips:
Seed ALLOWLIST with your management IP or VPN range.
Start with report-only: comment out the ban line and print candidates until you’re confident.
Keep Fail2Ban active—your script can hand bans to it, ensuring a single source of truth.
5) See what’s happening (fast)
A little visibility goes a long way. Here are quick ways to spot top offenders and trends.
Install lnav:
- Debian/Ubuntu (apt):
sudo apt install -y lnav
- Fedora/RHEL (dnf):
sudo dnf install -y lnav
- openSUSE (zypper):
sudo zypper install -y lnav
Open logs and filter:
sudo lnav /var/log/auth.log
# or
sudo lnav /var/log/secure
Quick-and-dirty top IPs in the last hour:
- Debian/Ubuntu:
sudo journalctl -u ssh --since "1 hour ago" | awk '/Failed password/ {print $(NF-2)}' | sort | uniq -c | sort -nr | head
- Fedora/RHEL/openSUSE:
sudo journalctl -u sshd --since "1 hour ago" | awk '/Failed password/ {print $(NF-2)}' | sort | uniq -c | sort -nr | head
CrowdSec metrics:
sudo cscli metrics
sudo cscli decisions list
Fail2Ban status:
sudo fail2ban-client status sshd
Real-world outcome
On a small VPS exposed to the internet:
Baseline hardening cut down successful attempts to zero (keys only).
Fail2Ban shrunk brute-force noise by >90% automatically.
CrowdSec blocked known repeat offenders before they could ramp up.
The local anomaly detector caught a slow, distributed spray that didn’t trip simple thresholds and fed the IPs into Fail2Ban.
Common pitfalls and how to avoid them
Lockouts: Always keep a second session open when changing SSH or banning logic.
Overblocking: Populate allowlists and start in “dry-run” mode for your anomaly script.
Port changing: Moving SSH off 22 reduces noise but is not a defense by itself. Do it if it helps your logs, but do not rely on it.
Neglected telemetry: Periodically check decisions and unban false positives.
Conclusion and next steps
You don’t need a data science team to add intelligence to SSH defense. Start with strong keys and policy, let Fail2Ban do the heavy lifting, tap into CrowdSec’s collective insight, and sprinkle in a small anomaly detector that feeds bans back into your existing tooling.
Your next steps: 1) Apply the baseline sshd_config and verify key-only access. 2) Install and enable Fail2Ban and CrowdSec. 3) Deploy the lightweight anomaly script in report-only mode, then enable bans. 4) Add a monthly review to tune thresholds and allowlists.
If you found this useful, harden one server today—then templatize it for the rest. Your logs are ready to fight back; give them the tools.