SSH Hardening and fail2ban#

Concepts#

Why Harden SSH?#

SSH is the front door to your server. A default SSH installation accepts password logins from any IP on port 22 — attackers constantly scan for this. Hardening SSH reduces the attack surface.

SSH Server Configuration#

The SSH server is configured in /etc/ssh/sshd_config:

sudo nano /etc/ssh/sshd_config

After any change, restart the SSH service:

sudo systemctl restart ssh        # Ubuntu
sudo systemctl restart sshd       # Debian (some installs)

Important: Before making changes, ensure you have an alternative way to access the machine (console, physical access, second SSH session) in case you lock yourself out.

Key Hardening Steps#

1. Disable Root Login#

Never allow direct root login over SSH:

# /etc/ssh/sshd_config
PermitRootLogin no

Log in as a regular user and use sudo instead.

2. Disable Password Authentication#

After setting up SSH keys (covered in Module 11), disable passwords entirely:

# /etc/ssh/sshd_config
PasswordAuthentication no
PubkeyAuthentication yes

This makes brute-force password attacks impossible — only key holders can log in.

3. Change the Default Port#

Moving SSH off port 22 reduces automated scanning:

# /etc/ssh/sshd_config
Port 2222

Then connect with:

ssh -p 2222 user@host

This is “security through obscurity” — it stops automated bots but not a determined attacker. Still worth doing because it dramatically reduces log noise.

Don’t forget to update your firewall:

sudo ufw delete allow 22
sudo ufw allow 2222/tcp

4. Limit Users#

Restrict which users can log in via SSH:

# /etc/ssh/sshd_config
AllowUsers kmiguel deploy
# OR allow by group:
AllowGroups sshusers

5. Idle Timeout#

Disconnect idle sessions:

# /etc/ssh/sshd_config
ClientAliveInterval 300      # send keepalive every 300 seconds
ClientAliveCountMax 2        # disconnect after 2 missed keepalives (= 10 min idle)

6. Limit Authentication Attempts#

# /etc/ssh/sshd_config
MaxAuthTries 3               # max failed attempts per connection
MaxSessions 5                # max concurrent sessions per connection
LoginGraceTime 30            # seconds to authenticate before disconnecting

7. Disable Empty Passwords#

# /etc/ssh/sshd_config
PermitEmptyPasswords no       # usually already the default

8. Use Strong Key Types#

# /etc/ssh/sshd_config
# Disable weak host key algorithms
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
# Remove DSA and ECDSA lines if present

Complete Hardened sshd_config Example#

# /etc/ssh/sshd_config — hardened settings

Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers kmiguel
X11Forwarding no

# Logging
LogLevel VERBOSE

After editing:

# Test config for syntax errors BEFORE restarting
sudo sshd -t
# If no output, the config is valid

sudo systemctl restart ssh

Checking for Attacks#

# Failed SSH login attempts
journalctl -u ssh | grep -i "failed" | tail -20

# Auth log
grep "Failed password" /var/log/auth.log 2>/dev/null | tail -20

# Count failed attempts by IP
grep "Failed password" /var/log/auth.log 2>/dev/null | \
    awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -10

fail2ban — Automatic Intrusion Prevention#

fail2ban monitors log files for repeated failed login attempts and temporarily bans the offending IP addresses by adding firewall rules.

Install and Enable#

sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban

Configuration#

fail2ban uses jails — each jail monitors a specific service:

# Default config (do not edit)
cat /etc/fail2ban/jail.conf

# Create a local override
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Always edit jail.local, not jail.conf — the .conf file is overwritten on updates.

Key Settings#

# /etc/fail2ban/jail.local

[DEFAULT]
# Ban duration (seconds). -1 = permanent
bantime  = 3600          # 1 hour ban
# Time window to count failures
findtime = 600           # 10 minutes
# Number of failures before ban
maxretry = 5
# Action: ban via ufw (or iptables, nftables)
banaction = ufw

# Email notifications (optional, requires mail setup)
# destemail = admin@example.com
# action = %(action_mwl)s

[sshd]
enabled  = true
port     = ssh           # or your custom port: 2222
logpath  = /var/log/auth.log
maxretry = 3
bantime  = 3600

Managing fail2ban#

# Status overview
sudo fail2ban-client status

# Status of SSH jail
sudo fail2ban-client status sshd

# Manually ban an IP
sudo fail2ban-client set sshd banip 10.0.0.5

# Manually unban an IP
sudo fail2ban-client set sshd unbanip 10.0.0.5

# View banned IPs
sudo fail2ban-client status sshd | grep "Banned"

# View fail2ban log
sudo tail -20 /var/log/fail2ban.log

How fail2ban Works#

1. fail2ban watches /var/log/auth.log
2. Detects: "Failed password for user from 10.0.0.5"
3. Counts failures from 10.0.0.5 in the last 10 minutes
4. If count >= maxretry (3): adds a firewall rule to block 10.0.0.5
5. After bantime (1 hour): removes the firewall rule

Custom Jails#

You can protect other services too:

# /etc/fail2ban/jail.local

[nginx-http-auth]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 5

Lab#

Exercise 1: Review Current SSH Config#

# View current settings
grep -v "^#" /etc/ssh/sshd_config | grep -v "^$"

# Check if root login is allowed
grep -i "PermitRootLogin" /etc/ssh/sshd_config

# Check authentication method
grep -i "PasswordAuthentication" /etc/ssh/sshd_config

Exercise 2: Check for Failed Logins#

# Recent failed attempts
journalctl -u ssh 2>/dev/null | grep -i "failed" | tail -10

# Auth log
grep "Failed" /var/log/auth.log 2>/dev/null | tail -10

# Successful logins
last | head -10

Exercise 3: Install and Configure fail2ban#

# Install
sudo apt install -y fail2ban

# Create local config
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# Enable and start
sudo systemctl enable --now fail2ban

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

Exercise 4: Test fail2ban (Carefully)#

# View fail2ban log
sudo tail -20 /var/log/fail2ban.log

# Check if any IPs are currently banned
sudo fail2ban-client status sshd

# View the firewall rules fail2ban added
sudo ufw status numbered 2>/dev/null
sudo iptables -L f2b-sshd 2>/dev/null

Review#

1. What is the most important SSH hardening step?

Disable password authentication and use key-based authentication only (PasswordAuthentication no). This makes brute-force password attacks impossible.

2. Why should you disable root login over SSH?

Root is a known username on every Linux system — attackers only need to guess the password. Disabling root login (PermitRootLogin no) forces attackers to guess both the username and the credential. Use a regular user and sudo instead.

3. How do you test sshd_config for errors before restarting?

sudo sshd -t. If there are no syntax errors, it produces no output. Always test before restarting to avoid locking yourself out.

4. What does fail2ban do?

It monitors log files (e.g., /var/log/auth.log) for repeated failed login attempts. When an IP exceeds the threshold (maxretry failures in findtime seconds), fail2ban adds a firewall rule to block that IP for bantime seconds.

5. Why edit jail.local instead of jail.conf?

jail.conf is overwritten when fail2ban is updated. jail.local overrides jail.conf and is preserved across updates.


Previous: AppArmor | Next: Automatic Updates and Auditing