Exclusive offer — -20% on all VPS plans with code NVH20 · Deployment in 60 seconds
Home / Documentation / Securing SSH
Security

Securing SSH on a VPS: keys, root disabled and Fail2ban

By NVHCloud Team 3 October 2026 6 min read

As soon as a server gets a public IP address, bots try to log in over SSH with lists of common passwords. This is not theoretical: run journalctl -u ssh | grep -c Failed on a VPS that has been online for a few days, and the count is often in the thousands. This guide makes those attempts useless.

1. Why it is the first thing to do

A password can be guessed, reused or leaked. An ED25519 SSH key, in practice, cannot be guessed. Three measures together remove most of the risk:

  • Key-only authentication: passwords are no longer accepted at all.
  • Root login forbidden: an attacker must also guess a username.
  • Fail2ban: addresses that keep trying are banned automatically.

2. Create and install an SSH key

On your computer:

ssh-keygen -t ed25519 -C "alex@laptop"

Choose a passphrase: if your computer is stolen, the key stays unusable. Then copy the public key to the user account on the server (created in the first steps):

ssh-copy-id alex@SERVER_IP

On Windows, where ssh-copy-id does not exist, the equivalent command is given in first steps on your VPS. Check that ssh alex@SERVER_IP works without asking for a password before going further.

3. Harden the configuration

Rather than editing the main /etc/ssh/sshd_config file, create a dedicated file in the configuration directory: it is not overwritten by updates and keeps your settings in one place.

cat > /etc/ssh/sshd_config.d/00-hardening.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
AllowUsers alex
EOF
AllowUsers only allows the listed accounts: separate them with spaces if you have several. MaxAuthTries drops the connection after three attempts.

4. The cloud-init trap

The file name starts with 00- for a reason. For each setting, OpenSSH keeps the first value it reads, and files in the directory are read in alphabetical order. Many Ubuntu images contain a 50-cloud-init.conf file with PasswordAuthentication yes. Named 99-..., your file would be read afterwards and silently ignored: passwords would still be accepted.

ls /etc/ssh/sshd_config.d/

To see which value really applies, ask the SSH server directly:

sshd -T | grep -E "permitrootlogin|passwordauthentication|kbdinteractive|allowusers"

5. Change the port (optional)

Moving away from port 22 does not stop an attacker targeting you, who will find the new port in seconds. It does, however, remove most automated attempts from your logs. If you do it, add the line Port 2222 to the file above, and open the port in the firewall first:

ufw allow 2222/tcp
⚠️ Ubuntu 22.10 and later start SSH through socket activation: the listening port is then defined by systemd, and simply restarting the service is not enough. After changing the port, run systemctl daemon-reload then systemctl restart ssh.socket. On Debian 12, systemctl restart ssh is enough.

6. Apply without locking yourself out

Check the syntax, then reload SSH. Do not close your current session.

sshd -t && systemctl reload ssh

Open a second terminal and test:

ssh alex@SERVER_IP                # must work
ssh root@SERVER_IP                # must be refused
ssh -o PubkeyAuthentication=no alex@SERVER_IP   # must be refused

If the first command fails, fix things from the session you kept open. If you changed the port, add -p 2222 to each command; once the new port works, remove the old firewall rule with ufw delete allow OpenSSH.

7. Install Fail2ban

Fail2ban reads the logs and bans, through the firewall, addresses that pile up failures. Even with passwords disabled, it reduces load and log noise.

apt install -y fail2ban
cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
banaction = ufw

[sshd]
enabled  = true
backend  = systemd
port     = ssh
maxretry = 3
bantime  = 24h
EOF
systemctl enable --now fail2ban
systemctl restart fail2ban

If you changed the port, replace port = ssh with port = 2222. backend = systemd is required on Debian 12, which no longer writes /var/log/auth.log by default; it sits in the [sshd] section rather than [DEFAULT] so it does not interfere with jails that read log files later, such as Nginx ones.

8. Verify

fail2ban-client status sshd
journalctl -u ssh --since "1 hour ago" | tail

The first command shows how many addresses are banned. Then add an allow-list firewall: configuring UFW.

Frequently asked questions

What if I locked myself out of my VPS?
Contact support to get console access without SSH. Delete or fix /etc/ssh/sshd_config.d/00-hardening.conf, then reload SSH. This is exactly why you should always test from a second terminal.
Why do SSH passwords still work after setting PasswordAuthentication no?
Almost always because another file is read before yours, such as 50-cloud-init.conf on Ubuntu: OpenSSH keeps the first value it finds. Prefix your file with 00- and check the applied value with sshd -T.
RSA or ED25519 keys?
ED25519: keys are shorter, faster and considered at least as secure as a 4096-bit RSA key. RSA is only useful for very old systems that do not support ED25519.
Is Fail2ban useful if passwords are disabled?
Yes, to a lesser extent: attempts can no longer succeed, but they use resources and fill the logs. Fail2ban cuts them short, and its real value shows when you extend it to other services, such as a website login page.