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.