A fresh DigitalOcean Droplet with default SSH settings will see its first brute-force attempt within minutes of going online. Real-world data shows 145 failed SSH login attempts and 45 auto-bans in the first week for a typical internet-facing server targeted by bots scanning port 22. SSH is the front door of your VPS, and the default configuration is wide open to password-based attacks. This guide covers every hardening step I apply at Commsult Indonesia before any application goes live.
SSH key pairs are cryptographically immune to brute-force. A 4096-bit RSA key or an Ed25519 key has more possible combinations than atoms in the observable universe — no botnet can crack it by trying combinations. Password authentication is the attack surface; eliminating it eliminates the risk. Generate a key pair on your local machine (ssh-keygen -t ed25519 -C your@email.com), copy the public key to the server (ssh-copy-id user@server), verify you can log in with the key, then disable password authentication in sshd_config.
Edit /etc/ssh/sshd_config and set: PasswordAuthentication no, PermitRootLogin no, PubkeyAuthentication yes, MaxAuthTries 3, X11Forwarding no, AllowTcpForwarding no, PermitEmptyPasswords no, LoginGraceTime 30, ClientAliveInterval 300, ClientAliveCountMax 2. The ClientAlive settings disconnect idle sessions after 10 minutes, preventing ghost sessions from accumulating. Test the config with sshd -t before reloading — a syntax error will not prevent the existing session from working but will prevent new connections after a reload.
Moving SSH from port 22 to a non-standard port (e.g., 2222, 22222) eliminates the vast majority of automated scanning noise. Bots scan port 22 by default; very few scan non-standard ports comprehensively. This is security through obscurity and not a primary defense, but it reduces log noise by 90%+ and makes your fail2ban logs much easier to read. On DigitalOcean, always update the Cloud Firewall to allow the new port and block 22 before restarting sshd.
┌─────────────────────────────────────────────────────┐
│ SSH ATTACK DEFENSE LAYERS │
└─────────────────────────────────────────────────────┘
Internet
│
▼
Cloud Firewall ──► BLOCK: allow SSH only from known IPs
│
▼
SSH Port 2222 ──► Obscures automated port-22 scans
│
▼
sshd_config ──► Keys only, no root, MaxAuthTries 3
│
▼
Fail2ban ──► Auto-ban IPs after 3 failed attempts
│
▼
Your Server ──► Only legitimate key holders get inFrom my experience hardening VPS instances for Commsult Indonesia projects in Jakarta, always keep one terminal session active while testing SSH changes. The sequence I use: 1) Make the sshd_config change, 2) Run sshd -t to validate, 3) Open a second SSH session to verify the new config works, 4) Only then close the original session. This prevents being locked out. I have been locked out exactly once — on a GCP instance — and had to use the serial console to recover. It is a painful 20-minute experience that you only want to have once.
Even with password auth disabled, SSH scanners generate noise. Fail2ban monitors /var/log/auth.log, detects repeated failed login attempts, and adds iptables rules to ban offending IPs automatically. With default settings (5 failures in 10 minutes = 10-minute ban), fail2ban handles the automated scanning load without manual intervention. Configure a longer ban time (bantime = 1h or bantime = 24h) and lower maxretry (3) for more aggressive protection. The SSH jail is enabled by default in most fail2ban configurations.
Layer your defenses: the cloud provider firewall is your outermost defense and blocks traffic before it reaches your VPS. Use DigitalOcean Cloud Firewall or GCP VPC firewall rules to restrict SSH access to your known IP addresses only (your home IP, office IP, VPN IP). If your IP is dynamic, restrict SSH to a VPN-sourced IP instead. This is the most effective defense — traffic that never reaches your server cannot attempt authentication.
# Generate a modern Ed25519 key pair
ssh-keygen -t ed25519 -C "you@example.com"
# Copy public key to server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server
# Harden /etc/ssh/sshd_config
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
X11Forwarding no
AllowTcpForwarding no
PermitEmptyPasswords no
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers matthews deploy
KexAlgorithms curve25519-sha256,diffie-hellman-group16-sha512
# Validate and reload
sshd -t && systemctl reload sshd
# Install fail2ban SSH jail
apt install fail2ban -y
# /etc/fail2ban/jail.local
# [sshd]
# enabled = true
# port = 2222
# maxretry = 3
# bantime = 3600For maximum security, add TOTP-based two-factor authentication via Google Authenticator PAM module (libpam-google-authenticator). This requires both your SSH key and a time-based OTP even if someone steals your private key. Set up: install the PAM module, run google-authenticator as the target user, configure /etc/pam.d/sshd to require it, and enable ChallengeResponseAuthentication in sshd_config. The trade-off is operational overhead: every SSH session requires entering an OTP. For shared team accounts, this is impractical. For root or admin accounts on high-value servers, it is worth the friction.
I learned this the hard way: when you add AllowUsers username to sshd_config and reload, ALL OTHER USERS are immediately denied SSH access — including root and any other user you did not explicitly list. I added AllowUsers matthews to a shared VPS without listing the deploy user, and our CI/CD pipeline stopped working immediately because it used a separate deploy user for deployments. Always list all SSH users in AllowUsers and test with a non-privileged session before applying changes.
After hardening, audit your setup: run ssh-audit (available at ssh-audit.com) against your server to check for weak algorithms and ciphers. Review /etc/ssh/sshd_config for any remaining weak settings. Check that fail2ban is running and has active bans (fail2ban-client status sshd). Scan your server with nmap from an external host to verify only the expected ports are open. Set up a weekly cron job to email you a summary of SSH login attempts and bans.
A production-ready minimal sshd_config for Ubuntu 22.04: disable password and root login, enable key-based auth, set MaxAuthTries 3, restrict to specific users with AllowUsers, use modern key exchange algorithms only (KexAlgorithms curve25519-sha256,diffie-hellman-group16-sha512), and enable logging at VERBOSE level. The VERBOSE log level captures key fingerprints in the auth log, which helps identify which key was used for a login — critical for auditing on multi-user servers.