How to Secure a VPS (2026): Hardening Checklist

The short version: Secure a fresh Ubuntu 24.04 VPS by creating a non-root sudo user, switching SSH to key-only auth, and disabling root login. Then set a default-deny UFW firewall that opens only the ports you use, install fail2ban, turn on automatic security updates, remove unused services, and run a Lynis audit. Budget about 30 minutes.

I have hardened a few hundred VPS boxes over the years, on Hetzner, Cloudways, and others, and the pattern almost never changes. A fresh VPS ships with three gaps wide open, attackers start probing your IP within minutes of it going live, and the fix takes well under an hour. This is the exact checklist I run on every new server, with the why next to each step so you are not just pasting commands blind.

The three gaps every fresh VPS ships with

Here is what you are actually fixing. These three defaults are real and they sit on almost every provider's base image.

Default gap Why it matters
Root SSH login is enabled Root is the one username every bot already knows. Password-guessing scripts start hammering root the minute your IP is live.
No firewall is configured Every port your software opens is reachable from the entire internet. A forgotten database or admin port becomes a way in.
No fail2ban or brute-force protection Failed login attempts run forever with no penalty, so an attacker can keep trying credentials and flooding your logs.

Steps 1 to 4 below close all three. Everything after that is hardening on top of a box that is already safe from the common automated attacks.

What you need

If you do not have a key pair yet, run ssh-keygen -t ed25519 on your local machine and accept the defaults. That writes a private key to ~/.ssh/id_ed25519 and a public key to ~/.ssh/id_ed25519.pub. The public key is the one you copy to the server. Never share or upload the private key.

Step 1. Create a non-root sudo user

Working as root all day is how a single bad command (or a single compromised process) wrecks the whole box. Create a normal user, give it sudo, and use that for everything. You keep full admin power through sudo, you just stop exposing the root account directly. Log in as root first, then run:

adduser deploy
usermod -aG sudo deploy

Set a strong password when prompted (you will rarely type it, but sudo needs one). Now copy your SSH key to the new user so you can log in as deploy without a password. The cleanest way from your local machine is:

ssh-copy-id deploy@YOUR_SERVER_IP

If ssh-copy-id is not available, do it manually on the server while logged in as deploy:

su - deploy
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "PASTE_YOUR_PUBLIC_KEY_HERE" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Open a new terminal and confirm ssh deploy@YOUR_SERVER_IP logs you in with the key, no password. Keep your root session open until you have done this. Locking yourself out is the most common way to ruin step 2.

Step 2. Harden SSH: key-only, no root, no passwords

This is the highest-value step on the page. Right now your server accepts password logins for any user, including root, from the entire internet. We turn that off and accept only SSH keys. As deploy, edit the SSH daemon config:

sudo nano /etc/ssh/sshd_config

Set these three values (uncomment them if they already exist, or add them):

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

On Ubuntu 24.04 the cloud image often drops an override file in /etc/ssh/sshd_config.d/ that re-enables password auth, so it pays to set it there too rather than fight a file that wins on load order. The reliable approach is to write a small drop-in that takes precedence:

echo -e "PermitRootLogin no\nPasswordAuthentication no\nPubkeyAuthentication yes" | sudo tee /etc/ssh/sshd_config.d/99-hardening.conf
sudo sshd -t
sudo systemctl restart ssh

The sshd -t command validates the config before you restart. If it prints nothing, the syntax is good. On Ubuntu 24.04 the service is socket-activated and named ssh, not sshd, which trips up older guides.

Now the critical test. Without closing your current session, open a new terminal and run ssh deploy@YOUR_SERVER_IP. If it logs you in with your key, you are safe to close the old session. If it asks for a password or fails, do not log out, fix the config from your still-open session first.

A non-standard SSH port is optional. It adds no real security (a port scan finds it instantly), but it cuts the constant noise of bots that only target port 22. If you want it, set Port 2222 in the drop-in, allow that port in UFW in step 3 (before you restart SSH), and connect with ssh -p 2222. Do not rely on the port change instead of key-only auth.

Step 3. Set a default-deny firewall with UFW

A firewall is the difference between "only the ports I chose are reachable" and "everything my software happens to open is on the public internet." Ubuntu ships UFW (Uncomplicated Firewall), a front end to nftables. The rule: deny everything inbound by default, then allow back only what you serve.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable

OpenSSH is an application profile that allows port 22. If you moved SSH to a custom port in step 2, allow that port number instead (for example sudo ufw allow 2222/tcp) and do it before you enable UFW, or you will lock yourself out. If you are running a web server, open HTTP and HTTPS too:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Check what is actually open with sudo ufw status verbose. The most common mistake here is opening a database port (3306 for MySQL, 5432 for Postgres) to the world. Do not. Keep databases bound to 127.0.0.1 or a private network and never add a UFW rule that exposes them publicly.

Step 4. Install and tune fail2ban

With key-only auth on, password brute-force against SSH is already pointless. We still run fail2ban because it bans IPs that hammer the box, keeps the auth log readable, and protects other services that log failures. Install it and create a local override (never edit jail.conf directly, package upgrades overwrite it):

sudo apt update && sudo apt install -y fail2ban
sudo nano /etc/fail2ban/jail.local

Paste this minimal config. It bans an IP for one hour after 4 failed attempts within 10 minutes, and it whitelists localhost so you never ban yourself:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
bantime  = 1h
findtime = 10m
maxretry = 4

[sshd]
enabled = true

On Ubuntu 24.04, fail2ban reads SSH events from systemd's journal by default, so you do not need to point it at a log file. Enable and start it, then confirm the jail is live:

sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

The status output shows banned IPs and total failures. Within a day on a public IP you usually see a handful of bans already, which is the background scanning that was hitting you before.

Step 5. Turn on automatic security updates

Most servers that get popped are not hit by a clever zero-day. They run a package with a public CVE and a patch that shipped weeks ago, because nobody logged in to run apt upgrade. Fix that once and forget it:

sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

Answer "Yes" to enable it. By default it installs security updates only, which is the safe choice (you do not want a feature update silently changing behavior on a production box). Confirm it is active and do a dry run:

sudo systemctl status unattended-upgrades
sudo unattended-upgrade --dry-run --debug

Kernel patches still need a reboot to take effect. Either schedule a maintenance reboot window, or enable Canonical Livepatch on Ubuntu for reboot-free kernel updates if uptime matters. For most single-server setups, a monthly reboot at a quiet hour is plenty.

Step 6. Remove unused services and shrink attack surface

Every service listening on a port is something an attacker can probe. See exactly what is exposed:

sudo ss -tulpn

That lists every listening TCP and UDP socket with the process behind it. Anything bound to 0.0.0.0 or [::] is reachable from outside (subject to your firewall). Common culprits on default images are an unused mail transfer agent, an old print service, or a sample web server you never asked for. Disable and remove cleanly:

sudo systemctl disable --now SERVICE_NAME
sudo apt purge -y PACKAGE_NAME
sudo apt autoremove -y

Do not get carried away removing things you are unsure about, this is where people break their own box. The goal is simple: if it listens on a port and you do not use it, it should not be running.

Step 7. Optional: add 2FA to SSH

For a high-value box (a client server, payment data, anything you would hate to lose), layer a TOTP second factor on top of your SSH key. Even a stolen private key is then not enough to get in. Install the PAM module and run the setup as the user who logs in:

sudo apt install -y libpam-google-authenticator
google-authenticator

Answer "yes" to time-based tokens, scan the QR code into your authenticator app, and save the emergency scratch codes somewhere offline. Then enable it for SSH by adding the module to the PAM stack and writing a drop-in that turns on the TOTP prompt and requires both your key and the code:

echo "auth required pam_google_authenticator.so" | sudo tee -a /etc/pam.d/sshd
printf 'KbdInteractiveAuthentication yes\nAuthenticationMethods publickey,keyboard-interactive\n' | sudo tee /etc/ssh/sshd_config.d/98-2fa.conf
sudo sshd -t
sudo systemctl restart ssh

The AuthenticationMethods publickey,keyboard-interactive line is what makes SSH ask for the TOTP code after your key succeeds. Without it, key auth alone lets you straight in and the second factor never fires. Test from a new session before logging out, same rule as always. Most personal projects do not need this, but for anything where a leaked key is a real disaster, it is worth the two minutes.

Step 8. Audit the box with Lynis

Now confirm the work. Lynis is a free open-source auditing tool that scans the system, scores your hardening, and prints concrete suggestions. Install and run it:

sudo apt install -y lynis
sudo lynis audit system

It finishes with a hardening index out of 100 and a "Suggestions" section. After the steps above, expect a score in the 70s, which is solid for a single server. Work through the suggestions that match your use case (file permission tweaks, additional sysctl settings, kernel hardening) and re-run the audit. Do not chase 100; the last points come from compliance-grade settings most apps do not need. Lynis tells you what to fix, it does not fix anything for you.

Common mistakes that lock you out (or leave you open)

Skip the manual work: managed hardening

Everything above is straightforward once you have done it a few times, but if you never want to touch a Linux terminal, you can have hardening handled for you. A managed host like Cloudways applies SSH hardening, a firewall, OS and patch management, bot protection, and free SSL at the platform level, so the entire checklist on this page is done for you and kept current. You give up some low-level control and pay a markup over a raw box, but for people who want a secure server without administering it, the trade is worth it.

If you do want to run the hardening yourself, do it on a box that gives you the most value. We run this exact checklist on a Hetzner CX22 (EUR 4.50/mo for 4 GB RAM, 2 vCPU, 40 GB NVMe, 20 TB traffic), the machine every command above was tested on. See our best cheap VPS hosting guide for the full ranked list.

FAQ

What are the most important steps to secure a VPS?

The three highest-impact steps are: switch SSH to key-only authentication and disable root login, turn on a default-deny firewall (UFW) that only allows the ports you use, and install fail2ban to auto-block brute-force attempts. Those three close the gaps that almost every fresh VPS ships with. After that, enable automatic security updates and run a Lynis audit.

Should I disable root SSH login on a VPS?

Yes. Root is the one username every attacker already knows, so leaving root SSH login on hands them half the credential for free. Create a normal user with sudo, log in as that user, and set PermitRootLogin no in the SSH config. You keep full administrative power through sudo without exposing the root account to the open internet.

Do I need fail2ban if I already use SSH keys?

Key-only auth already makes password brute-force pointless, so fail2ban is not strictly required for SSH security once passwords are off. We still run it because it cuts the noise of constant login attempts out of your logs, bans IPs that hammer the box, and protects other services (web app logins, mail) where it can read the logs. It is cheap insurance, so we keep it.

Is changing the SSH port a real security measure?

Moving SSH off port 22 is security through obscurity, not real protection. It does cut automated bot noise that targets port 22, which keeps logs cleaner, but a real attacker just scans your ports and finds it. Treat a non-standard port as a minor noise filter, never as a substitute for key-only auth, a firewall, and fail2ban.

How do I keep an Ubuntu VPS patched automatically?

Install the unattended-upgrades package and enable it with dpkg-reconfigure. By default it installs security updates on its own, so you are not exposed to a known CVE just because you forgot to run apt upgrade. For kernel patches that need a reboot, either schedule a reboot window or add Livepatch for reboot-free kernel updates.

What is Lynis and should I run it?

Lynis is a free open-source security auditing tool for Linux. It scans your system, scores your hardening, and prints a list of concrete suggestions (file permissions, SSH settings, missing tools). Run it after your initial hardening to catch anything you missed, then re-run it after big changes. It does not fix things for you, it tells you what to fix.

Can I get VPS security handled for me instead of doing it by hand?

Yes. A managed host like Cloudways applies SSH hardening, a firewall, OS and patch management, and bot protection at the platform level, so you do not run these steps yourself. You give up some low-level control and pay a markup over a raw Hetzner box, but for people who do not want to administer Linux it removes the whole hardening checklist from your plate.