Every week, hosting resellers spin up a fresh VPS, install a control panel, and start provisioning customer accounts—all before locking down SSH. That's a critical mistake. A compromised root shell means every cPanel account, every database, every WordPress install on that box is gone.
This guide walks you through the exact SSH hardening steps you should run on every new server before it touches production traffic. Ten minutes, one reboot, and your attack surface drops dramatically.
Prerequisites#
You'll need:
- A fresh Linux server (Ubuntu 22.04/24.04, AlmaLinux 9, or Debian 12)
- Root or sudo access via SSH (password-based is fine for now—we're about to fix that)
- A local machine with
ssh-keygenavailable (macOS, Linux, or Windows with OpenSSH)
Step 1: Generate an Ed25519 Key Pair on Your Local Machine#
Ed25519 keys are shorter, faster, and more secure than RSA-4096. On your local machine:
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519_servernameSet a strong passphrase when prompted. The private key (id_ed25519_servername) never leaves your machine. The public key (id_ed25519_servername.pub) goes on the server.
Step 2: Create a Non-Root Admin User#
SSH into your server as root, then:
adduser deploy
usermod -aG sudo deployOn RHEL/AlmaLinux, replace sudo with wheel. Now copy your public key to the new user:
mkdir -p /home/deploy/.ssh
cat >> /home/deploy/.ssh/authorized_keys < ~/.ssh/id_ed25519_servername.pub
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.sshTest it—open a new terminal and verify you can log in as deploy with your key:
ssh -i ~/.ssh/id_ed25519_servername [email protected]If that works, you're ready to harden the daemon. Do not close your existing root session yet.
Step 3: Harden the SSH Daemon Configuration#
Back up the current config, then edit it:
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
nano /etc/ssh/sshd_configSet or uncomment these directives (remove duplicates if needed):
Port 22
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
PermitEmptyPasswords no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowTcpForwarding no
AllowUsers deployA few notes:
- `PermitRootLogin no`—the single most impactful change. Attackers brute-force root constantly.
- `PasswordAuthentication no`—forces key-based auth. No key, no entry.
- `AllowUsers deploy`—whitelist. Even if another user exists, they can't SSH in.
- `MaxAuthTries 3`—disconnects after three failed attempts per session.
If you use a non-standard port, replace Port 22 with e.g. Port 2222. Security through obscurity isn't real protection, but it does cut log noise from automated scanners by roughly 90%.
On Ubuntu 24.04 and newer, also check /etc/ssh/sshd_config.d/ for drop-in files that might override your settings:
grep -r PasswordAuthentication /etc/ssh/sshd_config.d/If any drop-in re-enables password auth, fix or remove it.
Step 4: Validate and Reload SSH#
Before restarting the daemon, validate syntax:
sshd -tIf there are no errors, reload:
systemctl reload sshdKeep your existing root session open. Open a second terminal and test deploy login with your key. If it fails, you still have the root shell to fix things.
Step 5: Install and Configure fail2ban#
fail2ban watches log files and bans IPs after repeated failures. Install it:
# Debian/Ubuntu
apt install fail2ban -y
# AlmaLinux/RHEL
dnf install epel-release -y && dnf install fail2ban -yCreate a local config (never edit jail.conf directly):
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
banaction = iptables-multiport
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
EOFOn RHEL-family, change logpath to /var/log/secure. Start and enable it:
systemctl enable --now fail2banVerify it's running:
fail2ban-client status sshdYou should see Currently banned: 0 and Currently failed: 0. After a few hours of internet exposure, that banned count will climb—that's fail2ban doing its job.
Step 6: Enable Automatic Security Updates#
Unattended upgrades close known CVEs while you sleep:
# Debian/Ubuntu
apt install unattended-upgrades -y
dpkg-reconfigure -plow unattended-upgrades
# AlmaLinux/RHEL
dnf install dnf-automatic -y
sed -i 's/apply_updates = no/apply_updates = yes/' /etc/dnf/automatic.conf
systemctl enable --now dnf-automatic.timerStep 7: Verify Everything#
Run through this checklist from a new terminal:
ssh [email protected]—should be rejected immediately.ssh [email protected]—should prompt for your key passphrase, then log in.sudo whoami—should returnroot(confirm sudo works).fail2ban-client status sshd—daemon is active.sshd -t—no config errors.
If all five pass, close your old root session. You're done.
Wrapping Up#
This ten-minute lockdown eliminates the two most common initial attack vectors on hosting servers: password brute-force and root login exploitation. It's not a complete security posture—you still need a firewall (UFW or firewalld), kernel tuning, and application-level isolation—but it's the foundation everything else rests on.
If you're running a multi-server operation with tools like Salieno Core, bake these steps into your provisioning templates so every new node ships hardened by default. The worst time to think about SSH security is after you've already loaded customer data onto the box.
0 comments
Loading comments…