Thapa Technical — Dev Blog
VPS Ubuntu SSH Security UFW

Setting Up a VPS in 2026 — Secure It From Scratch.

A fresh VPS comes wide open: root logs in with a password, and bots are knocking within minutes. Before you deploy a single app, you do the first-hour checklist — log in over SSH, update packages, create a sudo user, switch to key-only login, kill password and root access, and put a firewall in front. This is Part 1: by the end your server is locked down and ready for real work.

What You'll Need

This whole setup-and-hardening pass takes about 20–25 minutes. Have these ready:

  • A VPS running Ubuntu (24.04 LTS in these screenshots) and its public IP address
  • The root password your provider emailed you (you'll retire it by the end)
  • A terminal on your own machine — PowerShell/Windows Terminal, macOS Terminal, or any Linux shell
  • 20 minutes and a willingness to copy/paste carefully
Throughout this guide, replace thapa with your own username and the example IP 62.72.59.218 with your server's real IP.
⚡ Reader Deal — Don't Skip This

Need a VPS? Get one for the price of a coffee.

Every screenshot below is from a Hostinger KVM VPS — full root access, NVMe storage, and a real public IP. Grab it through the link below and stack our code on top of their current sale:

Use coupon code thapa7 for an extra 10% OFF on top of the existing discount.
🚀 Claim My VPS Deal →

Affiliate link — it costs you nothing extra and helps keep these guides free. Thank you! 💚

1
Provision

Spin Up Your VPS

Order a VPS, pick Ubuntu 24.04 as the operating system, and choose a data-center region close to your users (this one is in Mumbai). Once it boots, your provider's dashboard shows the two things you need: the public IP address and the root password.

Hostinger VPS overview panel showing IP, hostname, Ubuntu 24.04, Running status and the Reboot VPS button
VPS Dashboard Click to expand
Why the IP matters

The public IP is your server's address on the internet — you'll SSH to it now, and later point your domain's DNS at it. Keep this dashboard tab open; the Reboot VPS button here is your safety net if you ever lock yourself out.

2
Connect

Log In Over SSH as Root

SSH (Secure Shell) is the protocol you use to securely control a remote server. The ssh client ships by default on Linux, macOS and Windows. Open a terminal on your own computer and connect using the server's IP — here root is the username Hostinger gave us (yours may differ), and the part after @ is the IP:

bash — local machine
ssh root@62.72.59.218

The very first time, SSH asks you to verify the server's fingerprint. Type yes to trust it — it's saved on your machine and checked on every future connection:

SSH asking to confirm the ED25519 host key fingerprint on first connection
Host Fingerprint Prompt Click to expand

Enter the root password from your dashboard when prompted. You're now inside the server — the prompt changes to something like root@srv416761:~#.

Why the fingerprint check matters

On later connections SSH re-validates that saved fingerprint. If it suddenly doesn't match and you're blocked, treat it as a red flag — the server may have been tampered with or you're connecting to an impostor.

3
Update

Update & Upgrade Packages

The first thing to do on any new server is refresh the package list and upgrade everything that's outdated. Ubuntu is Debian-based, so we use APT:

bash — on your VPS (root)
apt update
apt upgrade -y

apt update only refreshes the list of available packages from the repositories — it doesn't change anything installed. apt upgrade then installs the newer versions. On a Red Hat–based distro (CentOS, Fedora) you'd swap apt for dnf.

Some upgrades (like a new kernel) need a reboot. Check for the marker file — if it exists, a reboot is required:

bash — on your VPS (root)
cat /var/run/reboot-required

You can reboot straight from your provider's dashboard (the Reboot VPS button you saw earlier), or over SSH:

bash — on your VPS (root)
reboot
4
User

Create a Non-Root Sudo User

Living as root full-time is risky — one wrong command runs with unlimited power. Create a regular user instead. You'll be asked to set a password (make it different from root's) and can press Enter through the optional details:

bash — on your VPS (root)
adduser thapa
Output of the adduser thapa command creating a new user and home directory
adduser Click to expand

By default this user can't run admin commands. Add it to the sudo group so it can — only when prefixed with sudo — then confirm:

bash — on your VPS (root)
usermod -aG sudo thapa
groups thapa
usermod -aG sudo thapa followed by groups thapa showing thapa sudo users
Grant sudo Click to expand

Seeing thapa : thapa sudo users confirms it worked. Exit and log back in as the new user:

bash — local machine
ssh thapa@62.72.59.218

As a normal user you no longer have automatic admin powers. Run an admin command directly and you'll hit a wall — that's the security working as intended:

apt update failing with Permission denied because it was run without sudo
Permission Denied Click to expand

The fix is simply to prefix admin commands with sudo and enter your user's password when asked:

bash — on your VPS (thapa)
sudo apt update
Why bother with a separate user

If an app or a leaked credential gets compromised, a non-root user limits the blast radius — the attacker can't touch the whole system without your sudo password. It's the single highest-value habit in server security.

5
Keys

Set Up SSH Key Authentication

Passwords can be brute-forced; SSH keys effectively can't. On your own machine (not the server), generate a modern ed25519 key pair. Press Enter to accept the default location and optionally set a passphrase:

bash — local machine
ssh-keygen -t ed25519 -C "you@example.com"

# On a legacy system without ed25519 support, use RSA:
ssh-keygen -t rsa -b 4096 -C "you@example.com"
ssh-keygen generating an ed25519 key pair with randomart image
ssh-keygen Click to expand

Keys are stored in your .ssh folder (C:\Users\You\.ssh on Windows, ~/.ssh on macOS & Linux). You'll see two files — the distinction is critical:

Listing of .ssh folder showing id_ed25519 private key and id_ed25519.pub public key
Key Files Click to expand
FileWhat it isRule
id_ed25519Private keyNever share it. Stays on your machine.
id_ed25519.pubPublic keySafe to copy — this is what goes on the VPS.

Now put the public key on the server. The easiest way is ssh-copy-id (macOS, Linux and Git Bash):

bash — local machine
ssh-copy-id thapa@62.72.59.218

If you'd rather do it by hand (or you're on plain Windows PowerShell): connect as the non-root user, create the .ssh folder, and paste your public key into an authorized_keys file:

bash — on your VPS (thapa)
mkdir -p ~/.ssh
nano ~/.ssh/authorized_keys
# paste the full contents of id_ed25519.pub, then Ctrl+O, Enter, Ctrl+X

Now test it — you should log straight in with no password prompt (or just your key's passphrase):

bash — local machine
ssh thapa@62.72.59.218
Do not move to the next step until key login works. If you disable passwords before your key is accepted, you'll lock yourself out. Note: Hostinger's "add SSH key" option only configures it for root — we want it on the non-root user, so add it manually.
6
Windows

Enable the OpenSSH Agent (Windows Only)

If you set a passphrase on your key, the OpenSSH Authentication Agent can remember it so you don't retype it every connection. The quickest way is two commands in PowerShell (as administrator):

powershell — as administrator
Get-Service -Name ssh-agent | Set-Service -StartupType Automatic
Start-Service ssh-agent

Prefer a GUI? Open Services from Windows Search, find OpenSSH Authentication Agent, right-click it and choose Properties:

Windows Services window with OpenSSH Authentication Agent selected and Properties highlighted
Services → Properties Click to expand

Set Startup type to Automatic, click Start, then OK:

OpenSSH Authentication Agent properties with Startup type set to Automatic
Startup: Automatic Click to expand

Then add your key to the agent (only needed if you used a non-default key name or a passphrase):

powershell — local machine
ssh-add $env:USERPROFILE\.ssh\id_ed25519
macOS / Linux users

You can skip this step — the SSH agent is already running. Just run ssh-add ~/.ssh/id_ed25519 if you want your passphrase cached for the session.

7
Harden

Disable Password & Root Login

With key login confirmed, it's time to slam the door on passwords and root. On the server, open the SSH daemon config:

bash — on your VPS (thapa)
sudo nano /etc/ssh/sshd_config

Find these lines, uncomment them if needed, and set them like so — this keeps key auth on and turns password auth off:

sshd_config with PubkeyAuthentication yes and PasswordAuthentication no
sshd_config Click to expand
/etc/ssh/sshd_config
PubkeyAuthentication yes
PasswordAuthentication no
The gotcha that traps everyone on cloud VPSes: Ubuntu cloud images ship an override file that re-enables passwords, silently undoing your change.

On Hostinger that file is 50-cloud-init.conf inside /etc/ssh/sshd_config.d/ (note: sshd_config.d, not ssh_config.d). Look at it:

bash — on your VPS (thapa)
cd /etc/ssh/sshd_config.d/
ls
sudo cat 50-cloud-init.conf
50-cloud-init.conf in sshd_config.d containing PasswordAuthentication yes
The Override File Click to expand

Edit it and change its yes to no so it stops overriding you:

bash — on your VPS (thapa)
sudo nano /etc/ssh/sshd_config.d/50-cloud-init.conf
# set: PasswordAuthentication no

Now forbid root from logging in over SSH entirely. Back in the main config, find PermitRootLogin and set it to no:

sshd_config with PermitRootLogin no added
PermitRootLogin no Click to expand
/etc/ssh/sshd_config
PermitRootLogin no

Save each file (Ctrl+O, Enter, Ctrl+X), then restart SSH to apply everything:

bash — on your VPS (thapa)
sudo systemctl restart ssh
# the service may be named sshd on CentOS/Fedora
Test before you close the session

Keep your current SSH window open. In a new terminal, confirm you can still log in as thapa with your key, and that ssh root@<ip> is now refused. If the new login works, you're safe to close the old window.

8
Firewall

Put Up a UFW Firewall

A firewall closes every port except the ones you explicitly open. Ubuntu (and Hostinger) ship with UFW, the Uncomplicated Firewall. If yours doesn't have it: sudo apt install ufw. Check its current state:

bash — on your VPS (thapa)
sudo ufw status

It'll likely report inactive. Set a default-deny posture — block all incoming, allow all outgoing:

bash — on your VPS (thapa)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw default deny incoming — default incoming policy changed to deny
Default Deny Click to expand
MOST IMPORTANT — allow SSH first. SSH uses port 22. If you enable the firewall before allowing OpenSSH, your next reconnect is blocked and you're locked out.

Allow SSH so you can keep managing the server (if you changed the SSH port, use sudo ufw allow <port-number> instead):

bash — on your VPS (thapa)
sudo ufw allow OpenSSH
sudo ufw allow OpenSSH — Rule added and Rule added v6
Allow OpenSSH Click to expand

We'll be hosting websites later, so open the web ports too, then turn the firewall on:

bash — on your VPS (thapa)
sudo ufw allow http   # same as 80/tcp
sudo ufw allow https  # same as 443/tcp
sudo ufw enable

Finally, confirm exactly what's open:

bash — on your VPS (thapa)
sudo ufw status
sudo ufw status active showing OpenSSH, 80/tcp, 443 and 21/tcp allowed for IPv4 and IPv6
Firewall Active Click to expand
Status active with only the ports you intended? Your server is now hardened — key-only login, no root over SSH, and a default-deny firewall. 🎉

Your Hardening Checklist

Here's everything you just locked down, at a glance:

DoneHardening step
Updated & upgraded all packages
Created a non-root user with sudo rights
Switched to SSH key authentication (ed25519)
Disabled password login — including the cloud-init override
Disabled root login over SSH
Enabled a default-deny UFW firewall with only SSH/HTTP/HTTPS open

Quick Troubleshooting

SymptomLikely fix
"Permission denied (publickey)"Key isn't in ~/.ssh/authorized_keys, or permissions are wrong — the file must be 600 and ~/.ssh must be 700.
Still being asked for a passwordThe 50-cloud-init.conf override is still set to yes — fix it and sudo systemctl restart ssh.
Locked out completelyUse your provider's browser terminal / VNC console to get in and undo the change.
Firewall blocking a serviceOpen its port: sudo ufw allow <port>/tcp, then re-check sudo ufw status.
📘 Continue the Series

Your server is secure. Now let's put something on it.

In Part 2 you'll take a full-stack app from a GitHub repo to a live HTTPS domain — DNS, PostgreSQL, Node.js, PM2 and Caddy, end to end on the VPS you just hardened.

Read Part 2 → Deploy on Your VPS

Deploying a Full-Stack App on a VPS — Start to Finish

Wrapping Up

A default VPS is an open invitation; a hardened one is a fortress. In one short session you updated the system, replaced password root login with key-only access for a limited sudo user, neutralised the cloud-init password override that catches almost everyone, and wrapped the whole thing in a default-deny firewall. That's the exact baseline every production server should start from — do it once, and every app you deploy afterwards inherits it.

Want to learn this end to end with real projects and mentorship? Our Full Stack AI Bootcamp covers building and deploying production apps on your own infrastructure.

Discussion

Leave a Reply
Ready to Ship Real Apps?
Learn Full-Stack Deployment Hands-On

Join our live online classes and learn to build and deploy production-ready full-stack apps — from a hardened server to a live HTTPS domain — with expert mentorship.

Explore the Bootcamp
Up next: /blog/host-fullstack-nextjs-postgresql-vps.php — Deploy on your VPS (Part 2)
Screenshot
VPS setup screenshot

  Scroll to see the full image  ·  Press ESC or click backdrop to close