Skip to main content
This script applies to VPS and Bare Metal servers. Make sure you have an SSH key pair configured before running this script — it will disable password authentication.

Before You Start

Generate an SSH Key Pair

If you don’t already have an SSH key, generate one on your local machine (not the server):
ssh-keygen -t ed25519 -C "your-email@example.com"

Copy Your Public Key to the Server

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server-ip
Verify you can log in with your key before proceeding:
ssh -i ~/.ssh/id_ed25519 user@your-server-ip
If you run this script without a working SSH key on the server, you will be locked out. Verify key-based login works before proceeding. If you do get locked out, use the VirtFusion console (VPS) or IPMI (Bare Metal) to recover access.

The Script

#!/bin/bash
set -euo pipefail

# --- Configuration ---
# Set to "yes" to change the SSH port, then set the desired port number.
CHANGE_PORT="no"
NEW_SSH_PORT=22

# Set to "yes" to install and configure Fail2Ban
INSTALL_FAIL2BAN="yes"
# ---------------------

SSHD_CONFIG="/etc/ssh/sshd_config"

echo "=== SSH Hardening ==="

# Detect distribution
if [ -f /etc/os-release ]; then
    . /etc/os-release
    DISTRO="$ID"
else
    echo "Cannot detect distribution."
    exit 1
fi

# Backup current sshd_config
cp "$SSHD_CONFIG" "${SSHD_CONFIG}.bak.$(date +%Y%m%d%H%M%S)"
echo "Backed up sshd_config"

# Function to set or add an sshd_config directive
set_sshd_option() {
    local key="$1"
    local value="$2"
    if grep -qE "^#?\s*${key}\s" "$SSHD_CONFIG"; then
        sed -i "s/^#*\s*${key}\s.*/${key} ${value}/" "$SSHD_CONFIG"
    else
        echo "${key} ${value}" >> "$SSHD_CONFIG"
    fi
    echo "  Set: ${key} ${value}"
}

# Disable root login
set_sshd_option "PermitRootLogin" "no"

# Disable password authentication (key-only)
set_sshd_option "PasswordAuthentication" "no"

# Disable empty passwords
set_sshd_option "PermitEmptyPasswords" "no"

# Disable challenge-response authentication
set_sshd_option "KbdInteractiveAuthentication" "no"

# Limit authentication attempts
set_sshd_option "MaxAuthTries" "3"

# Disable X11 forwarding (not needed on servers)
set_sshd_option "X11Forwarding" "no"

# Change SSH port if configured
if [ "$CHANGE_PORT" = "yes" ]; then
    set_sshd_option "Port" "$NEW_SSH_PORT"
    echo ""
    echo "*** SSH port changed to $NEW_SSH_PORT ***"
    echo "*** Update your firewall rules before restarting SSH ***"
fi

# Validate sshd config before restarting
echo ""
echo "Validating sshd_config..."
sshd -t

# Restart SSH
echo "Restarting SSH..."
systemctl restart sshd

echo ""

# --- Fail2Ban ---
if [ "$INSTALL_FAIL2BAN" = "yes" ]; then
    echo "=== Installing Fail2Ban ==="
    case "$DISTRO" in
        debian|ubuntu)
            apt update -qq
            apt install -y fail2ban
            ;;
        almalinux|rocky|centos|rhel|cloudlinux|fedora)
            if command -v dnf &>/dev/null; then
                dnf install -y epel-release 2>/dev/null || true
                dnf install -y fail2ban
            else
                yum install -y epel-release
                yum install -y fail2ban
            fi
            ;;
        arch|manjaro)
            pacman -Sy --noconfirm fail2ban
            ;;
        opensuse*|sles)
            zypper install -y fail2ban
            ;;
        *)
            echo "Manual Fail2Ban installation required for $DISTRO"
            INSTALL_FAIL2BAN="no"
            ;;
    esac

    if [ "$INSTALL_FAIL2BAN" = "yes" ]; then
        # Determine the SSH port for Fail2Ban
        F2B_PORT="ssh"
        if [ "$CHANGE_PORT" = "yes" ]; then
            F2B_PORT="$NEW_SSH_PORT"
        fi

        # Create Fail2Ban local jail config
        cat > /etc/fail2ban/jail.local << EOF
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
banaction = iptables-multiport

[sshd]
enabled  = true
port     = $F2B_PORT
logpath  = %(sshd_log)s
backend  = %(sshd_backend)s
maxretry = 3
bantime  = 24h
EOF

        systemctl enable fail2ban
        systemctl restart fail2ban

        echo "Fail2Ban installed and configured."
        echo "  SSH jail: max 3 retries, 24-hour ban"
    fi
fi

echo ""
echo "=== SSH Hardening Complete ==="
echo ""
echo "Summary:"
echo "  Root login:          disabled"
echo "  Password auth:       disabled (key-only)"
echo "  Max auth tries:      3"
echo "  X11 forwarding:      disabled"
[ "$CHANGE_PORT" = "yes" ] && echo "  SSH port:            $NEW_SSH_PORT"
[ "$INSTALL_FAIL2BAN" = "yes" ] && echo "  Fail2Ban:            active"
echo ""
echo "A backup of your original sshd_config was saved."

Usage

Edit the configuration variables at the top of the script before running:
# To change the SSH port, set:
CHANGE_PORT="yes"
NEW_SSH_PORT=2222

# To skip Fail2Ban installation, set:
INSTALL_FAIL2BAN="no"
Then run:
chmod +x ssh-harden.sh
./ssh-harden.sh

If You Change the SSH Port

If you change the port, update your firewall before disconnecting:
    ufw allow 2222/tcp
    ufw delete allow 22/tcp
    ufw reload
Then connect using the new port:
ssh -p 2222 user@your-server-ip

Verifying Fail2Ban

# Check Fail2Ban status
fail2ban-client status

# Check the SSH jail specifically
fail2ban-client status sshd

# Unban an IP if needed
fail2ban-client set sshd unbanip 203.0.113.50

What the Script Changes

SettingBeforeAfter
Root loginyes (default)no
Password authenticationyes (default)no (key-only)
Empty passwordsyes (default)no
Max auth tries6 (default)3
X11 forwardingyes (default)no
Fail2BanNot installed3 retries, 24-hour ban