In part one, we covered why a new VPS starts getting hammered with login attempts within minutes of going online, and the shape of the response: key-based SSH, no root login, a firewall, automatic blocking, patched software. That post stayed at the level of ideas. This one is the follow-up: the actual commands, plus the next layer of hardening once the basics are in place.
The first five steps below are the essentials from part one, with the commands attached. If you have already done these, skip ahead to going further, where we get into two-factor sudo access, log monitoring, and a Docker gotcha that quietly undoes a firewall for a lot of people without them noticing.
The essentials, recapped with commands
Step 1: get off the root account
A fresh VPS typically hands you a single account: root, with full and unrestricted access to everything. Working from it day to day is worth avoiding, since a single mistyped command has no safety net. Create a named account and add it to the sudo group instead:
adduser yourusername
usermod -aG sudo yourusername
Set a strong password while you are there, ideally a long random string rather than something memorable, since this account is about to become the only door into the server:
openssl rand -base64 24
passwd yourusername
Step 2: switch SSH to key-based login
Passwords can be guessed. A properly generated SSH key effectively cannot. Generate one on your own machine, not the server, and copy it across:
ssh-keygen -t ed25519 -C "your-email@example.com"
ssh-copy-id yourusername@your-server-ip
Confirm key-based login actually works, in a new terminal window, before touching anything else:
ssh yourusername@your-server-ip
Once that is confirmed, edit /etc/ssh/sshd_config and turn off the two things that let a password guesser in at all:
PermitRootLogin no
PasswordAuthentication no
sudo systemctl restart ssh
Keep your current session open until a fresh key-based login is confirmed working in a second window. Restarting SSH with a broken config, or before your key is verified, is the single most common way people lock themselves out of their own server.
This is covered in far more depth, including named-user and group-based access rules, in our free guide on securing SSH on a new VPS.
Step 3: put a firewall in place, default-deny
With SSH locked down, the next question is what else can reach the server at all. On Ubuntu and Debian, UFW is the usual starting point. The principle that matters is default-deny: block everything inbound, then explicitly allow only what you actually use.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable
Add rules for whatever else the server needs to expose, typically web ports, and nothing beyond that:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status verbose
Step 4: add automatic blocking for repeated failures
A firewall controls what can connect. It does not care how many times the same address just failed to log in. fail2ban closes that gap, watching your logs and temporarily banning anything that crosses a failure threshold. A basic SSH jail is running in a couple of minutes:
sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
The default configuration already covers SSH. CrowdSec is a reasonable alternative that adds shared threat intelligence from other servers on top of the same basic idea, if you want to go a step further. Both are covered, with the trade-offs between them, in our guide to the basic security layers every server needs.
Step 5: turn on automatic security updates
Most break-ins exploit a known, already-patched hole in something nobody got round to updating. Unattended upgrades close that gap quietly, in the background, without needing you to remember:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
That is five steps, and a working, reasonably hardened VPS. None of it takes more than an hour, and it is enough to take a server out of the "easy target" pile that most automated attempts are actually fishing in. The rest of this post is for anyone who wants to go a step further.
Going further: the next layer
Everything below is optional in the sense that a server without it is not defenceless. It is worth doing once the essentials are settled and you want to close off the gaps a determined, rather than automated, attempt might find.
Harden SSH access a little more
Two small additions on top of the key-based setup from step 2. First, protect the private key itself with a passphrase, so a stolen laptop does not hand over server access along with it:
ssh-keygen -p -f ~/.ssh/id_ed25519
Second, consider moving SSH off port 22. This does not stop a targeted attacker, who will find the real port in seconds, but it does drop your server out of sight of the blunt, port-22-only scanners that make up a large share of the daily noise, which keeps your logs quieter and easier to actually read:
Port 2222
If you change the port, update your firewall rule and any fail2ban jail settings to match before restarting SSH, and keep your current session open until a fresh connection on the new port is confirmed working.
Add two-factor authentication for sudo
A stolen or guessed key is rare once password login is off, but it is not impossible. Requiring a one-time code alongside the key for anything run with sudo adds a second, independent barrier. The Google Authenticator PAM module is the common way to do this on Ubuntu and Debian:
sudo apt install libpam-google-authenticator
google-authenticator
The setup wizard gives you a QR code to scan into an authenticator app on your phone, along with a set of one-time backup codes worth storing somewhere safe. From there, PAM needs to be told to actually ask for it, which is a config change worth getting right in a test session before you rely on it for your main login.
Get into the habit of watching your own logs
fail2ban and CrowdSec react automatically, but nothing replaces occasionally looking at what is actually happening on the server yourself. journalctl is already there on any systemd-based system:
sudo journalctl -u ssh --since "24 hours ago"
If checking in manually is not going to happen reliably, a tool like logwatch can summarise the important bits into a daily email instead, which is a much lower-effort habit to actually keep up:
sudo apt install logwatch
Check whether Docker is quietly bypassing your firewall
This one catches people out often enough that it is worth calling out on its own. Docker manipulates iptables directly when you publish a container port, and it can do this in a way that bypasses UFW's rules entirely. A container port you thought was blocked can still be reachable from the internet, with UFW itself showing no sign anything is wrong.
If you run containers, verify what is actually exposed rather than trusting ufw status alone:
sudo iptables -L DOCKER -n
If that turns up ports you did not mean to expose, bind the container to localhost and put it behind a reverse proxy instead of publishing it directly, or look into a UFW-Docker integration script that closes this specific gap. Either way, this is worth checking once and rechecking any time you add a new container.
This is a hardening checklist, not a complete security programme for your specific setup. See our disclaimer for how to read blog content like this, and our full day-by-day hardening timeline for the fuller picture.
What this does not cover
Hardening reduces the chance of a break-in, however determined the attempt. It does not make a server invincible, and it is not a substitute for backups. Hardware still fails, mistakes still happen, and nothing above protects you from either. A tested, offsite backup is what turns an incident into an inconvenience rather than a disaster, and it deserves its own checklist entirely.
How we help
Techster Consulting can talk you through each of these steps on your own server, from the essentials in part one through to the extras above, explaining why every setting matters rather than just handing you commands to paste. You stay in control throughout: we advise, explain, and point you in the right direction, and you implement it at your own pace.
If you would rather have someone check your reasoning before you touch a production server, or want the fuller picture beyond what these two posts cover, get in touch and we will talk through where your setup currently stands.