Provision a fresh VPS and run this before you do anything else:

tail -f /var/log/auth.log

Watch what happens. Within minutes — sometimes seconds — brute force attempts start rolling in. Bots hammer port 22 around the clock, cycling through root, admin, ubuntu, pi, deploy, postgres, git, and every other default username in the dictionary. This is not targeted. It is automated, continuous, and pointed at every default-configured server on the public internet.

Seven lines in /etc/ssh/sshd_config close 95 percent of that surface area.

The Seven Changes

Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 20
AllowUsers deploy ryan

Port: Move Off 22

Port 22 is the first thing every scanner checks. Moving to a non-standard port does not provide security by obscurity — an attacker with a target can port-scan and find your SSH service wherever it lives. What it does provide is signal reduction.

Once you leave port 22, auth.log goes quiet. The noise from automated scanners disappears, and actual threats become visible instead of being buried in thousands of lines of bot traffic. That signal clarity has real operational value.

Pick any port above 1024. 2222 is common enough that some scanners check it, so a random high port like 47291 is marginally better if you want maximum noise reduction.

PermitRootLogin: Never

The root account is the first credential every brute force attempt tries. Disabling root login means that even if an attacker guesses or steals a password for another account, they cannot directly obtain root access via SSH. They need a separate privilege escalation step.

Set this to no. Always. Use a dedicated deploy user and sudo for privileged operations.

PasswordAuthentication: The Most Important Setting

This is the change that eliminates brute force attacks entirely. A key pair cannot be guessed. The entire category of attack — credential stuffing, dictionary attacks, slow password spraying — becomes irrelevant the moment password authentication is disabled.

Before turning this off, ensure you have a working key pair and that your public key is in ~/.ssh/authorized_keys for every account you need to log in as. Test in a second terminal before closing your current session.

# Verify your key works before disabling passwords
ssh -i ~/.ssh/your_key user@yourserver -p 2222

MaxAuthTries and LoginGraceTime: Fail Fast

MaxAuthTries 3 limits the number of authentication attempts per connection to three. After three failures, the connection is closed. This slows down any attacker who somehow gets past the password authentication check.

LoginGraceTime 20 gives clients 20 seconds to complete authentication before the connection is dropped. The default is 120 seconds, which is unnecessarily generous and gives bots time to probe.

AllowUsers: The Last Line of Defense

This is the setting most guides omit and the one with the highest leverage. AllowUsers creates an explicit allowlist of accounts that are permitted to authenticate via SSH. Every other account on the system — root, www-data, postgres, any service account — is blocked from SSH access regardless of whether they have a valid key or password.

The security implication is significant: even if an attacker compromises a service account’s credentials through an application vulnerability, that account cannot be used to SSH into the system. The blast radius of a credential compromise is contained.

List only the accounts that genuinely need SSH access. For most servers that is one or two usernames.

Applying the Changes

After editing /etc/ssh/sshd_config, validate the configuration before restarting:

sshd -t

If that returns no output, the configuration is valid. Then restart the daemon and test in a second terminal:

sudo systemctl restart sshd
# Keep this terminal open — test login in a new terminal first

Locking yourself out of a remote server because you restarted SSH with a bad configuration is an avoidable bad afternoon.

Bonus: fail2ban

With PasswordAuthentication no in place, fail2ban is not strictly necessary — there are no password attempts to block. But it is a useful belt-and-suspenders layer that auto-bans IPs generating excessive failed authentication attempts, keeps your auth.log manageable, and provides an additional signal layer for monitoring.

apt install fail2ban
# Default config bans after 5 failures for 10 minutes
# Tune thresholds in /etc/fail2ban/jail.local

The Full Picture

These seven settings together mean that an attacker needs: a correct key pair, for an account that is on the allowlist, on the correct port, completing authentication in under 20 seconds, in three attempts or fewer. Each layer is independently insufficient — combined, they reduce your SSH attack surface to near zero.

Ten minutes of configuration. Years of protection.

Frequently Asked Questions

Does changing the SSH port actually improve security?

Changing the SSH port from 22 to a non-standard port does not prevent a determined attacker from finding your service via port scanning, but it eliminates the vast majority of automated bot traffic that only targets port 22. The primary benefit is signal clarity: auth.log becomes quiet enough that real threats are visible instead of buried in noise.

What is the single most important SSH hardening setting?

Setting PasswordAuthentication to ‘no’ in /etc/ssh/sshd_config is the most impactful change because it completely eliminates brute force and credential stuffing attacks. Without password authentication enabled, an attacker must compromise your private key file directly, which is a fundamentally different and far more difficult attack vector.

Why should you disable root SSH login?

The root account is the first username every automated brute force script attempts. Disabling it with PermitRootLogin no means that even if an attacker obtains valid credentials for another account, they must perform a separate privilege escalation step to gain root access, adding a required layer of effort and audit trail.

How do I apply SSH config changes without locking myself out?

Before restarting the SSH daemon, verify your new config has no syntax errors by running ‘sshd -t’ as root. Keep your current SSH session open, open a second session to confirm login works on the new port and with the new settings, and only then restart sshd with ‘systemctl restart sshd’.