👍 What we like
- ✓Uses robust Ed25519 SSH key authentication instead of passwords
- ✓Disables direct root login to prevent brute-force attacks
- ✓Configures UFW firewall and fail2ban for anti-bruteforce protection
- ✓Includes automatic security updates for continuous hardening
👎 What to watch
- ✕Requires 30-45 minutes for initial setup and configuration
- ✕Complexity may be challenging for absolute beginners
- ✕Risk of lockout if SSH keys are not verified before disabling password auth
📑 Contents ▾
- 01 Prerequisites
- 02 Step 1: First connection and system update
- 03 Step 2: Create a non-root user with sudo
- 04 Step 3: Generate and install an SSH key pair
- · On your local machine
- · Copy the public key to the VPS
- · Test key-based connection
- 08 Step 4: Harden the SSH server configuration
- 09 Step 5: Configure the UFW firewall
- 10 Step 6: Install fail2ban against bruteforce
- 11 Step 7: Enable automatic security updates
- 12 Step 8: Final hardening verification
- 13 Best practices and advanced hardening
- 14 FAQ
- · Is it really necessary to disable password authentication?
- · I lost my SSH key, how do I reconnect?
- · Do UFW and fail2ban overlap?
- · Does this guide also apply to Debian 12?
- · How much RAM do I need for a basic secure VPS?
- 20 Related topics
You’ve just ordered your first VPS, the welcome email gives you an IP address and a root password, and then… you’re staring at an empty terminal. This is exactly where 90% of your server’s security is determined. A freshly installed Ubuntu VPS is exposed to the Internet within minutes: bots scan port 22 continuously and attempt thousands of connections per day. Leaving password authentication enabled with an accessible root account is leaving the door wide open.
In this guide, we will start with a pristine Ubuntu 24.04 LTS VPS and transform it into a hardened server, ready to host any service in production. Everything is explained, every command is exact and tested: creating a non-root user, SSH key authentication, disabling password and root login, UFW firewall, anti-bruteforce protection with fail2ban, and automatic security updates. Expect to spend 30 to 45 minutes the first time.
Prerequisites
Before starting, ensure you have:
-
A freshly provisioned Ubuntu 24.04 LTS VPS (a reliable provider like Hetzner starting at €4/month, or OVHcloud works perfectly). If you’re still hesitating, our comparison of the best VPS for self-hosting will help you choose.
-
The public IP address of your VPS and the initial root password (provided via email).
-
A terminal on your local machine: Terminal on macOS/Linux, or PowerShell / Windows Terminal on Windows (OpenSSH has been integrated since Windows 10).
-
30 minutes of your time and a bit of rigor.
Throughout this tutorial, replace 203.0.113.10 with your server’s actual IP and selfhostr with your chosen username.
Step 1: First connection and system update
Connect for the first time as root using the password you received:
ssh root@203.0.113.10
Accept the server fingerprint (yes) then enter the password. You are now in the shell. Before doing anything else, update the system to fetch the latest security patches:
apt update && apt upgrade -y
If the kernel is updated, a reboot is required. Check with:
[ -f /var/run/reboot-required ] && echo "Reboot required" || echo "No reboot needed"
If a reboot is required, run reboot and reconnect after a minute.
Take this opportunity to set the server’s timezone and hostname:
timedatectl set-timezone Europe/Paris
hostnamectl set-hostname srv-selfhostr
Step 2: Create a non-root user with sudo
Working as root daily is bad practice: a single mistake can destroy the system, and a compromised service running as root gives the attacker full access. So, we create a dedicated user:
adduser selfhostr
Enter a strong password (this will serve as a safety net for sudo, not for SSH). The other fields (full name, etc.) can be left empty. Then, add this user to the sudo group:
usermod -aG sudo selfhostr
Verify that the user has the correct permissions:
groups selfhostr
The output must contain sudo. Do not close the root session just yet: we will first configure SSH keys and verify that we can connect with the new user.
Step 3: Generate and install an SSH key pair
Key-based authentication is infinitely more robust than a password. A modern Ed25519 key is impossible to brute-force within a reasonable timeframe.
On your local machine
Open a new local terminal (do not close the one connected to the VPS) and generate the key pair:
ssh-keygen -t ed25519 -C "selfhostr@$(hostname)" -f ~/.ssh/id_selfhostr
Accept the proposed location and, ideally, protect the key with a passphrase. You will get two files: id_selfhostr (private, never share it) and id_selfhostr.pub (public, to be placed on the server).
Copy the public key to the VPS
The simplest method, if available:
ssh-copy-id -i ~/.ssh/id_selfhostr.pub selfhostr@203.0.113.10
Enter the selfhostr user’s password. If ssh-copy-id is not available (notably on Windows), do it manually. On the server side (in the already open root session):
mkdir -p /home/selfhostr/.ssh
chmod 700 /home/selfhostr/.ssh
nano /home/selfhostr/.ssh/authorized_keys
Paste the content of your id_selfhostr.pub file, save (Ctrl+O, Enter, Ctrl+X), then set permissions:
chmod 600 /home/selfhostr/.ssh/authorized_keys
chown -R selfhostr:selfhostr /home/selfhostr/.ssh
Test key-based connection
Before disabling anything, open a new local terminal and test:
ssh -i ~/.ssh/id_selfhostr selfhostr@203.0.113.10
You should land in the shell without being asked for an SSH password (only the key’s passphrase, if set). If it works, the rest can be done safely. If it fails, do not touch the SSH config until key-based login is operational.
To make life easier, create an alias in ~/.ssh/config on your local machine:
Host selfhostr
HostName 203.0.113.10
User selfhostr
IdentityFile ~/.ssh/id_selfhostr
Port 22
You can then connect with a simple ssh selfhostr.
Step 4: Harden the SSH server configuration
This is the most important step. We will disable root login and password authentication. Logged in as selfhostr, edit the config via a dedicated file (Ubuntu 24.04 loads files from /etc/ssh/sshd_config.d/, which is cleaner than modifying the main file):
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
Paste the following content:
# Disables direct root login
PermitRootLogin no
# Disables password authentication (keys only)
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
# Enables public key authentication
PubkeyAuthentication yes
# Limits attempts and sessions
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
# Restricts access to the authorized user only
AllowUsers selfhostr
Save. Validate the syntax before restarting the service, otherwise you risk locking yourself out:
sudo sshd -t
If the command returns nothing, the config is valid. Reload SSH:
sudo systemctl restart ssh
Classic trap: Do not close your current session under any circumstances. Open a new terminal and test
ssh selfhostr. If key-based login still works, you’re good. If you are locked out, you still have the old session open to fix it. Most providers also offer a VNC/KVM console as a fallback in case of total lockout.
Let’s verify that root is indeed denied (this command should fail quickly):
ssh root@203.0.113.10
You should get Permission denied (publickey). Perfect.
Step 5: Configure the UFW firewall
By default, all ports are potentially exposed. UFW (Uncomplicated Firewall) allows you to let through only what you need. The golden rule: block everything incoming, allow everything outgoing, then open only the necessary ports.
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allow SSH before enabling the firewall (otherwise you cut off your own connection):
sudo ufw allow OpenSSH
If you plan to host a site or a reverse proxy, also open web traffic:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Enable the firewall and confirm with y:
sudo ufw enable
Check the status of the rules:
sudo ufw status verbose
You will get a list of open ports. Everything else is blocked incoming.
Security tip: If you want to further limit SSH bruteforce at the network level,
sudo ufw limit OpenSSHtemporarily blocks an IP that opens more than 6 connections in 30 seconds.
Step 6: Install fail2ban against bruteforce
Even with SSH keys, bots will continue to hammer your port 22 and pollute your logs. fail2ban monitors logs and automatically bans IPs that multiply failures.
sudo apt install -y fail2ban
Never modify jail.conf directly (it gets overwritten during updates). Create a jail.local file:
sudo nano /etc/fail2ban/jail.local
With this content:
[DEFAULT]
# Ban for 1 hour after 4 failures within a 10-minute window
bantime = 1h
findtime = 10m
maxretry = 4
# Never ban yourself (add your static IP if you have one)
ignoreip = 127.0.0.1/8 ::1
[sshd]
enabled = true
port = ssh
On Ubuntu 24.04, the default log backend is systemd, which is handled automatically. Enable and start the service:
sudo systemctl enable --now fail2ban
Verify that the SSH jail is active:
sudo fail2ban-client status sshd
You will see the number of currently banned IPs and the list. To unban an IP if needed:
sudo fail2ban-client set sshd unbanip 198.51.100.5
Step 7: Enable automatic security updates
An unpatched server is an easy target. The unattended-upgrades package automatically applies security patches.
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Select Yes in the window that appears. To go further, check the configuration file:
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
Ensure that the security updates line is uncommented:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Optionally, enable automatic reboot at night if a kernel patch requires it (only do this if a short planned downtime is not an issue):
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Run a dry test to verify everything works:
sudo unattended-upgrades --dry-run --debug
Step 8: Final hardening verification
Run a complete check. Here are the verifications that confirm your server is properly secured:
# SSH root login must be denied
sudo sshd -T | grep -E "permitrootlogin|passwordauthentication"
# Expected: permitrootlogin no / passwordauthentication no
# The firewall must be active
sudo ufw status | head -1
# Expected: Status: active
# fail2ban must be running
sudo systemctl is-active fail2ban
# Expected: active
# Auto-updates must be scheduled
systemctl is-enabled unattended-upgrades
# Expected: enabled
If all outputs match, your VPS is hardened according to 2026 best practices.
Best practices and advanced hardening
To go beyond the basic foundation:
-
Change the SSH port (from 22 to a high port like 49222) drastically reduces bot noise. This is security through obscurity, so it’s a complement, never a substitute for keys. Remember to update the UFW rule and the
Portdirective in the SSH config. -
Disable IPv6 if you don’t use it, or remember to duplicate your UFW and fail2ban rules for IPv6.
-
SSH 2FA via Google Authenticator (
libpam-google-authenticator) adds a TOTP layer for sensitive environments. -
Regular snapshots with your provider: this is your safety net in case of a mistake. Combine them with off-site backups (see our restic tutorial below).
-
Monitor connections with
sudo lastb(failures) andlast(successes), and regularly audit/var/log/auth.log. -
Principle of least privilege: every service you install next should run under its own user, never as root.
FAQ
Is it really necessary to disable password authentication?
Yes, absolutely. It is the measure with the most impact. As long as PasswordAuthentication yes is active, any bot can try to guess your password indefinitely. With Ed25519 keys, this attack becomes practically impossible. Simply keep a backup copy of your private key in a password manager or an encrypted vault.
I lost my SSH key, how do I reconnect?
Use the KVM/VNC rescue console provided by your host (Hetzner, OVH, etc. all offer one). Log in as root via this console, temporarily re-enable PasswordAuthentication yes or add a new public key to authorized_keys, then reload SSH. This is exactly why you should never lock yourself out without a Plan B.
Do UFW and fail2ban overlap?
No, they are complementary. UFW is a static firewall: it decides which ports are open or closed. fail2ban is dynamic: it analyzes application logs and temporarily bans IPs that misbehave on the ports you chose to open. Together, they cover both the attack surface and behavior.
Does this guide also apply to Debian 12?
95% yes. The apt, UFW, and fail2ban commands are identical. The main difference concerns SSH service management (the service name is ssh on both recent versions) and the fail2ban log backend. Adjust paths if necessary, the rest applies as is.
How much RAM do I need for a basic secure VPS?
Hardening itself consumes almost nothing: fail2ban, UFW, and unattended-upgrades fit within a few dozen MBs. A VPS with 1 or 2 GB of RAM is more than enough for the base. Size it based on the services you will host next. For an overview of affordable offers, check our selection of cheap VPS in 2026.
Related topics
Your VPS is now a clean and secure foundation. The logical next step: set up a reverse proxy to expose your services cleanly over HTTPS, then implement automatic encrypted backups. To stay updated on vulnerabilities, new self-hosted services, and VPS deals, join our Telegram watch bot.