Skip to content
StealthRDP
VPS Management

SSH Hardening Checklist: How to Secure a Linux VPS

SSH hardening for a new Ubuntu 24.04 VPS, step by step: SSH keys, sshd settings, UFW and Fail2ban, with a test at each step so you stay connected.

Published

9 min readBy StealthRDP Team

This SSH hardening checklist takes a new Ubuntu 24.04 LTS VPS from password logins as root to key-only logins for one named user, behind a firewall and Fail2ban. Every risky step includes a test you run while your current session stays open, and notes where Debian 12 differs. If your server runs Windows, our tips for securing Remote Desktop cover the same ground for RDP.

SSH hardening checklist at a glance

SSH hardening on a new server takes eleven steps, and the order matters: get a key login working for a sudo user before you switch off root and password logins, and allow SSH before you turn on the firewall.

Step Lockout risk
1. Automatic security updates None
2. A sudo user None
3-4. SSH key login None
5-7. No root login, no passwords Yes: test a second login
8. A new SSH port (optional) Yes
9. UFW firewall Yes
10. Fail2ban It can ban your own address
11. Unused services off None

Prepare the server: updates and a sudo user

As root, sudo is optional in the first two steps. Throughout, replace deploy with your user name and 203.0.113.10 with your server's IP address.

1. Update the system and turn on automatic security updates

Install pending updates first: apt update refreshes the package lists and apt upgrade installs the upgrades. [1]

Update the system
sudo apt update
sudo apt upgrade

On Ubuntu, security updates install automatically through the unattended-upgrades package, which is installed by default and runs once a day. Both values in /etc/apt/apt.conf.d/20auto-upgrades should be "1", and a dry run shows what it would install. [1]

Check automatic updates
cat /etc/apt/apt.conf.d/20auto-upgrades
sudo unattended-upgrade --dry-run -v
Expected /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

It does not reboot on its own, because Unattended-Upgrade::Automatic-Reboot is false by default, so reboot by hand when an update needs it. [1] On Debian 12, if the package or the file is missing, install unattended-upgrades and create the file with the two lines above.

2. Create a non-root sudo user

Do admin work as a named user with sudo, which asks for that user's own password, not root's. Members of the sudo group get full root access through it. [2]

Create the user and add it to the sudo group
sudo adduser deploy
sudo adduser deploy sudo

adduser asks for a password and a few optional details. [2] Make the password long: SSH will use keys, but sudo still asks for it. Then check the account; sudo whoami should print root.

Check sudo for the new user
su - deploy
sudo whoami

On Debian 12, if the sudo command is missing, install it as root with apt install sudo first.

Set up SSH key login

3. Generate an ed25519 key on your computer

Create the key pair on your own computer, not on the server. Ubuntu recommends the ed25519 type for its shorter keys and lower computing cost, and ssh-keygen asks for a passphrase that protects the private key. [3]

Linux or macOS
ssh-keygen -t ed25519

Keep the default file name. The private key is ~/.ssh/id_ed25519; the public key you copy to servers is ~/.ssh/id_ed25519.pub. [3]

On Windows, run the same command in PowerShell; it saves the pair in the .ssh folder of your user profile. Microsoft advises against an empty passphrase and warns that anyone who gets the private key can sign in as you. [4]

Windows PowerShell
ssh-keygen -t ed25519

4. Copy the public key to the server

On Linux and macOS, ssh-copy-id appends your public key to ~/.ssh/authorized_keys for the user on the server. [3]

Linux or macOS
ssh-copy-id deploy@203.0.113.10

Microsoft's guide copies keys with PowerShell or scp instead. [4] For a Linux server, this line appends your key and makes the folder and the file writable by you only:

Windows PowerShell
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | ssh deploy@203.0.113.10 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

sshd checks these permissions before it accepts a login, through StrictModes, which is on by default. [5] Then run ssh deploy@203.0.113.10 in a new terminal: you should get in without the account password. If not, follow the server's SSH log while you retry: [3]

On the server
sudo journalctl -fu ssh.service

Lock down sshd: root login, passwords and users

The next steps switch off root and password logins. Start only after your key login as the new user works, and keep that session open until a second login succeeds.

5. Write your settings in a drop-in file

On Ubuntu, /etc/ssh/sshd_config starts with Include /etc/ssh/sshd_config.d/*.conf, and OpenSSH keeps the first value it reads for most settings, so files in that folder override the main file. [3] Included files are read in lexical order and, for each keyword, the first value sshd obtains wins, [5] so name yours 00-hardening.conf.

/etc/ssh/sshd_config.d/00-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowUsers deploy

From the sshd_config manual:

  • PermitRootLogin no stops root from logging in over SSH. The default, prohibit-password, refuses passwords for root but still accepts keys. [5]
  • PasswordAuthentication no turns off password logins, which are allowed by default. [5]
  • KbdInteractiveAuthentication no turns off keyboard-interactive logins, a separate method that is also on by default; its old name is ChallengeResponseAuthentication. [5]
  • AllowUsers deploy (optional) lets only the listed users log in. Unlike most keywords, each AllowUsers line adds to the list. [5] Spell the name exactly, or nobody gets in.

Treat the sudo account like root from now on: Ubuntu's openssh notes call any account you can escalate to root from equivalent to root against outside attacks. [6] On Debian 12, first confirm the Include line with grep -n Include /etc/ssh/sshd_config.

6. Check for files that turn passwords back on

Since the first value wins, any file that sorts before yours beats it. Search the folder:

Find every file that sets these options
ls /etc/ssh/sshd_config.d/
sudo grep -riE 'passwordauthentication|kbdinteractive|permitrootlogin|allowusers' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/

On cloud images, cloud-init writes the SSH settings it manages to /etc/ssh/sshd_config.d/50-cloud-init.conf whenever sshd_config includes that folder. [7] If that file says PasswordAuthentication yes, it beats every file that sorts after it, which is why yours starts with 00. Change it to no too, so the files agree.

7. Test the config and restart SSH

Test the files before you restart: a bad setting can stop sshd from starting, and SSH may be your only way in. [3]

Test, then restart
sudo sshd -t
sudo systemctl restart ssh.service

Fix anything sshd -t reports first. After the restart, keep your session open and test from a second terminal:

From your computer, in a new terminal
ssh deploy@203.0.113.10
ssh root@203.0.113.10

The first login should work and the second should be refused. If your login fails, undo the change in the open session and check the SSH log from step 4. [3]

Should you change the SSH port?

Only if you want quieter logs. A new port moves SSH off the default, 22, [5] so bots that only try port 22 miss it, but a scan of all ports still finds SSH. Keys and disabled passwords do the real work.

8. Change the port on Ubuntu 24.04 (optional)

If UFW or another firewall is already on, allow the new port first. Keep your session open until a login on the new port works.

On Ubuntu 24.04, ssh.socket listens on port 22 through systemd socket activation. A generator turns the Port lines in sshd_config and its drop-in files into socket settings, so after a port change you reload systemd and restart ssh.socket. [6]

sshd accepts more than one Port line, [5] so keep 22 while you test the new port:

Add to /etc/ssh/sshd_config.d/00-hardening.conf
Port 22
Port 2222
Apply the port change on Ubuntu 24.04
sudo sshd -t
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket

Test the new port from a second terminal. Then delete Port 22, repeat the three commands and use port 2222 in steps 9 and 10.

From your computer, in a new terminal
ssh -p 2222 deploy@203.0.113.10

On Debian 12, check first whether the socket is in use. If it is inactive, restarting the service applies the change.

Debian 12
systemctl is-active ssh.socket
sudo systemctl restart ssh.service

Firewall, Fail2ban and open ports

9. Allow SSH in UFW, then enable it

Enabling UFW can drop your SSH session, and from then on it blocks every port you have not allowed. Add the SSH rule before you run ufw enable.

UFW is installed disabled and, once enabled, denies incoming traffic by default. Its manual warns that ufw enable may drop SSH connections, so add the SSH rule first. [8]

Use limit for SSH rather than allow. A limit rule still allows connections but denies an address that tries to open 6 or more within 30 seconds. Do not add both rules, because UFW uses the first rule that matches. [8]

Rate-limit SSH, then enable UFW
sudo ufw limit 22/tcp
sudo ufw enable
sudo ufw status verbose

UFW asks for confirmation when you enable it over SSH. [8] If you moved SSH, limit 2222/tcp instead. On Debian 12, install UFW first if the command is missing. To take SSH off the public internet altogether, reach it only through a WireGuard private network. For protection further out, see our DDoS protection checklist for VPS hosting.

10. Add Fail2ban for the sshd jail

Fail2ban can ban your own IP address after repeated failed logins. Put your fixed IP address in ignoreip if you have one, and keep your session open while you test.

On Ubuntu 24.04, the package ships /etc/fail2ban/jail.d/defaults-debian.conf, which enables the sshd jail, sets backend = systemd and bans through nftables. [9] The systemd backend reads the systemd journal, so the jail needs no separate log file. [10]

Install Fail2ban and read the package defaults
sudo apt install fail2ban
cat /etc/fail2ban/jail.d/defaults-debian.conf

Fail2ban's own jail.conf bans an address for 10 minutes once it fails 5 times within 10 minutes, and tells you not to edit that file: put your changes in jail.local. [10]

/etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
ignoreip = 127.0.0.1/8 ::1 198.51.100.7

Replace 198.51.100.7 with your own fixed address, or delete it. The stock jail uses port = ssh, so add port = 2222 if you moved SSH. [10] On Debian 12, keep backend = systemd here and install python3-systemd, the systemd Python library this backend uses. [10] Then restart Fail2ban and check that it runs:

Apply the settings
sudo systemctl restart fail2ban
sudo systemctl status fail2ban

11. Check what else is listening

Every open port is another way in. UFW's listening report shows each listening TCP port and open UDP port, its address (* means every interface), the program behind it and the rules that apply. [8]

List listening ports and their rules
sudo ufw show listening

A service bound to 127.0.0.1 is reachable only from the server. Switch off public ones you do not need with systemctl disable --now, as Ubuntu's openssh notes do for ssh.socket. [6]

Switch off a service you do not need
sudo systemctl disable --now SERVICE_NAME

If you lock yourself out

Most lockouts come from closing the last working session too early. If it happens anyway:

  • Use a session that is still open to undo the last change, run sshd -t and restart.
  • Pause if UFW refused you: its limit rule counts attempts over 30 seconds. [8]
  • Wait out a Fail2ban ban, which lasts for bantime, an hour in the jail.local above, [10] or connect from another network, such as a phone hotspot.
  • Rebuild the server as a last resort. On StealthRDP, the Help Center explains how to rebuild a server from the client area: pick the operating system, wait 3-5 minutes and connect over SSH. A rebuild installs a fresh system, so you restore your data from a backup; our guide to automated VPS backups helps you set one up in time.

Unsure? StealthRDP support is available 24/7 through Telegram, client-area tickets and info@stealthrdp.com.

FAQ

What is SSH hardening?

SSH hardening means changing the SSH server's settings so fewer accounts and login methods get in: keys instead of passwords, no root login and, optionally, only named users. OpenSSH accepts password and keyboard-interactive logins by default, so those come first. [5]

Why does PasswordAuthentication no not work on Ubuntu?

Another file probably sets it first. Ubuntu's sshd_config includes /etc/ssh/sshd_config.d/ at the very top, and OpenSSH keeps the first value it finds. [3] On cloud images, check 50-cloud-init.conf there, where cloud-init writes its SSH settings. [7]

Does changing the SSH port improve security?

Only a little. It moves SSH off the default port 22, which bots that only try that port then miss, but a full port scan still finds it. [5] Keys and disabled passwords do far more.

Should I disable root login over SSH?

Yes, once a sudo user can log in with a key. PermitRootLogin no refuses root entirely, while the default, prohibit-password, still accepts root keys. [5] Ubuntu's openssh notes add that your sudo account deserves the same care as root. [6]

Do I need Fail2ban if SSH uses keys only?

It is optional but useful: it bans addresses that keep failing, by default after 5 failures within 10 minutes. [10] On Ubuntu 24.04, the package enables its sshd jail by default. [9]

Is UFW enabled by default on Ubuntu?

No. UFW is installed disabled, and once enabled it denies incoming connections that no rule allows. [8] Add a rule for SSH before you run ufw enable.

Sources & references
  1. Automatic updates — Ubuntu Server documentation (accessed 2026-11-03)
  2. User management — Ubuntu Server documentation (accessed 2026-11-03)
  3. OpenSSH server — Ubuntu Server documentation (accessed 2026-11-03)
  4. Key-based authentication in OpenSSH for Windows — Microsoft Learn (accessed 2026-11-03)
  5. sshd_config(5): OpenSSH daemon configuration file — OpenBSD manual pages (accessed 2026-11-03)
  6. openssh: debian/README.Debian (ubuntu/noble-updates) — Ubuntu (Launchpad) (accessed 2026-11-03)
  7. cloudinit/ssh_util.py — GitHub (canonical/cloud-init) (accessed 2026-11-03)
  8. ufw - program for managing a netfilter firewall — Ubuntu Manpages (accessed 2026-11-03)
  9. fail2ban: debian/debian-files/jail.d_defaults-debian.conf (ubuntu/noble-updates) — Ubuntu (Launchpad) (accessed 2026-11-03)
  10. fail2ban 1.0.2: config/jail.conf — GitHub (fail2ban/fail2ban) (accessed 2026-11-03)

Continue learning