The first hour is not optional
The internet's IPv4 space is small enough that scanning all of it takes a well-funded stranger a few minutes and a laptop a few hours. Hosting ranges are published, catalogued and rescanned constantly, so a new address is not obscure — it is a fresh entry in a list that already exists. In practice the first automated SSH login attempt on a newly booted box arrives long before you have finished reading your welcome email, and it will be followed by thousands more from unrelated sources, all of them trying the same few hundred passwords against the same few usernames. None of that is aimed at you. It is a machine sorting the internet into answers and does not answer, and the only thing that decides which pile you land in is what you did in the first hour.
The good news is that the work is boring and finite. Four controls do almost all of it: authenticate with keys instead of passwords, refuse every inbound port you are not deliberately serving, apply security updates without being asked, and stop operating as root. Each takes a few minutes and none of it is exotic. What makes it worth writing down specifically for an offshore, no-KYC server is the asymmetry at the other end — the recovery path. On a mainstream host, a bad sshd_config ends with a support ticket, an ID check and a console session. Here there is no identity on file to check, which is the entire point of the product and also the reason a careless keystroke costs you the machine rather than twenty minutes. Every step below is written with that in mind.
What actually attacks a small server
Two very different populations show up in the logs and it is worth separating them, because they are defeated by different things. The overwhelming majority is indiscriminate mass scanning: bots working through the address space looking for SSH that accepts passwords, databases bound to 0.0.0.0, admin panels with default credentials, forgotten staging copies, unpatched web applications with a public exploit. This population does not care what your server is for. It cannot be reasoned with, it never stops, and it is beaten completely by the checklist in this guide — not because the checklist is clever, but because the bots are looking for machines that skipped it and there are plenty of those.
The second population is targeted — someone who wants your specific box — and it is rare, expensive and almost never gets in through SSH. It arrives through the application you deployed, a dependency you did not audit, a credential you reused, or a laptop that was compromised before it ever touched the server. That is why hardening does not end at the firewall: the real questions become what your service runs as, what it can reach outbound, and how quickly you patch. Worth knowing while you plan: our VPS plans are KVM full virtualization, so you run your own kernel and the whole toolbox — nftables, ufw, namespaces, seccomp, custom sysctl — actually works, which is not true of container-based "VPS" products where the kernel belongs to somebody else.
SSH: keys, and exactly one door
Password authentication on a public SSH port is the single largest self-inflicted risk on a rented server, and removing it is the highest-value five minutes in this guide. Generate an Ed25519 keypair on your own machine — never on the server, where the private half would be born on the very host you are trying to protect — protect it with a passphrase, and load it into an agent so the passphrase costs you one prompt per session rather than one per login. Copy the public half up, confirm it works, and only then turn passwords off. With PasswordAuthentication no and KbdInteractiveAuthentication no in place, the thousands of daily guesses stop being a risk and become mere noise: there is no password to guess, so the attempt fails before it is even interesting.
Root deserves its own decision. PermitRootLogin prohibit-password keeps key-based root access for emergencies; PermitRootLogin no is stricter and forces every session through a named account with sudo, which is what you want the moment more than one person touches the box. Add AllowUsers so a stray account created by some package can never be a login path. Moving the daemon off port 22 is worth doing, but be honest about why: it is not a security control — anyone who scans your address will find the new port in seconds — it is log hygiene, and it removes the great majority of automated noise so that the entries left in auth.log are ones you should actually read. Whatever you change, validate with sshd -t before reloading, and keep your current session open until a second terminal has connected successfully. That habit is the difference between a typo and a lost server.
Default-deny, and the half of the firewall nobody configures
A firewall on a server has one job: make the answer to "what is listening here?" equal to the list of things you deliberately published. Default-deny inbound, allow outbound, then open ports one at a time with a reason attached to each. Before you write a single rule, run ss -tulpen and read what is already bound — a default install listens on more than most people expect, and a database or cache that binds to 0.0.0.0 instead of 127.0.0.1 is the classic way a small server ends up in somebody's dataset. Bind local services to loopback first; the firewall is your second line, not your only one.
Then there is the half that gets skipped. Every plan here ships a /64 of IPv6 alongside its IPv4, and most modern daemons happily bind both families. If your rules only cover v4, a service you believe is firewalled is reachable over v6 by anyone who resolves the address — and IPv6 scanning of a known hosting prefix is entirely routine. ufw does handle both, but only when IPV6=yes is set in /etc/default/ufw; verify with ufw status verbose rather than assuming. Docker deserves the same suspicion: publishing a container port with -p inserts rules into its own iptables chain that are evaluated before ufw's, so a container you thought was protected is often wide open. Bind it to 127.0.0.1:port explicitly and put a reverse proxy in front. Whatever you configure, test it from somewhere else on the internet — a rule that has only ever been read is a rule that has never been tested.
Updates you do not have to remember
Unpatched software is how most small servers are actually lost, and the reason is human rather than technical: patching is a chore that competes with everything else you have to do. Automate the security channel and the problem disappears. unattended-upgrades on Debian and Ubuntu applies security updates on a timer and stays out of the way; scope it to the security pocket rather than every available upgrade, so a routine feature release never reboots your application at three in the morning. This matters more on a self-managed offshore box than on a managed platform, because nobody is patching it for you and no account manager is going to email you about a critical CVE — there is no email address on file to reach.
Kernel updates need a restart to take effect, so decide your reboot policy deliberately instead of discovering it. needrestart will tell you which services are still running against deleted libraries, and an Unattended-Upgrade::Automatic-Reboot window in the small hours is fine for a stateless service. One important interaction: if you have followed our full-disk encryption guide and your root filesystem is encrypted, an automatic reboot stops at a passphrase prompt and stays there until you unlock it over the network. Either keep automatic reboots off on those machines, or make sure remote unlock is tested and you are awake for the window. Whichever you choose, write it down — a policy that lives only in your head stops existing the moment you are on holiday.
Rate limiting, fail2ban, and the noise floor
Once password authentication is off, brute force against SSH cannot succeed. It is worth being clear about what tools like fail2ban buy you after that point: not a defence against guessing — that is already impossible — but a quieter log, less CPU spent on doomed handshakes, and a genuine control on the layers where secrets are still guessable. Point it at the places that matter: a web application's login form, a mail server's SMTP AUTH, an admin path someone is enumerating. ufw limit gives you a cheap connection-rate cap without any extra software at all. A handful of kernel knobs are worth the same five minutes — SYN cookies on, reverse-path filtering on, ICMP broadcast responses off, and IPv6 router advertisements ignored on a server that has a static address.
Know where the host stops, though. None of this survives a volumetric attack, because a flood fills the network pipe well before it troubles the CPU: by the time packets reach your nftables rules they have already consumed the bandwidth you were trying to protect. That is why L3/L4 scrubbing of up to 1.5 Tbps sits upstream of the fleet and is included on every plan rather than sold as an extra, with an optional L7 shield for application-layer floods that look like legitimate requests. Your host firewall handles precision; the network handles volume. Treating either as a substitute for the other is how people end up surprised.
Locking yourself out is the real risk here
Of everything that can go wrong in this first hour, the overwhelmingly most likely outcome is not a break-in. It is you, at the end of a long session, reloading a broken sshd_config or enabling a firewall whose allow rule contains a typo, and discovering that the door has closed behind you. On a mainstream provider that is an annoyance. Here it deserves real respect, because registration is a handle and a password with eight recovery codes and no email anywhere in the loop, precisely so there is no identity to leak — and a provider that cannot identify you cannot identify you back into your server either. Nobody can vouch for you. That property is the product working as designed, and it is why the safeguards below are habits rather than suggestions.
Four of them cost nothing. Take a snapshot before you touch SSH or the firewall, so a bad reload is a rollback rather than a rebuild. Enrol a second public key from a different device — a phone, a work laptop, an offline backup — because one key on one disk is one spilled coffee away from being no key at all. Keep a second terminal connected while you change anything that could sever the first, and always test the new configuration from a fresh connection before you let go of the old one. And accept where the recovery ladder ends: snapshots live on the same host and are a rollback convenience, not a backup, so anything you would genuinely miss belongs in off-host storage — the daily encrypted backups add-on, or your own client-side encrypted copies pushed somewhere else. Your worst case should be redeploy and restore, measured in minutes, rather than gone.
SP·08What to add next, depending on what the box does
The checklist above is the floor, and it is the same for every machine. What goes on top depends entirely on the job. A public web server wants TLS, a reverse proxy terminating it, the application running as an unprivileged user, and the database on loopback or on another box entirely. A VPN endpoint is a different shape — one UDP port, no web stack, no public services at all — and is worked end to end in rolling your own WireGuard VPN. A mail server is the most demanding of the three and needs rDNS control on the address, which every plan includes; self-hosting mail on an offshore VPS covers the deliverability half. A Tor relay is deliberately the opposite of anonymous — it is a published, contactable service — and the tradeoffs are set out in running a relay on a no-KYC VPS. Anything holding private state at rest should also be encrypted, which is a separate control with its own failure modes, covered in LUKS and remote unlock.
It is worth ending where hardening sits in the larger picture, because it is only one of three layers and they fail independently. What the host knows about you is the first, and here it is close to nothing: a handle funded by a prepaid crypto balance from $30.00, no card and no document in the chain — the subject of paying for hosting anonymously. Whose law applies is the second, decided by where the hardware sits across our 6 regions rather than by where you sit, and worked region by region in which offshore location should you pick?. What the machine itself allows is the third, and that one is yours alone — no provider can configure it for you. A VPS from $8.00/mo is online in about 15 min, which means the hour that follows is where the actual security of the thing is decided. Spend it deliberately.
SP·09Step by step
-
01
Deploy, then get in before anything else
Deploy from the panel and pick a distribution you will actually keep patched — a current Debian or an Ubuntu LTS is the boring, correct answer. A VPS is online in about 15 min and its root credentials land in your panel. Connect straight away, bring the system fully up to date, and set a hostname so later logs are readable. Nothing else happens on this box until the rest of these steps are done.
ssh root@203.0.113.10 apt update && apt full-upgrade -y hostnamectl set-hostname edge-01 apt install -y ufw unattended-upgrades needrestart
-
02
Move off root: a named user, a key, and sudo
Generate the keypair on your laptop, never on the server. Then create a named account on the box, put the public half in its
authorized_keys, and give itsudo. Open a second terminal and log in as that user right now — before you change anything else, while root over SSH still works as a fallback.# on your own machine ssh-keygen -t ed25519 -C "laptop" ssh-copy-id -i ~/.ssh/id_ed25519.pub root@203.0.113.10 # on the server adduser --disabled-password --gecos "" ops usermod -aG sudo ops install -d -m 700 -o ops -g ops /home/ops/.ssh cp /root/.ssh/authorized_keys /home/ops/.ssh/ chown ops:ops /home/ops/.ssh/authorized_keys chmod 600 /home/ops/.ssh/authorized_keys
-
03
Lock the SSH daemon down — with a second terminal open
Write the changes to a drop-in file rather than editing the shipped config, so a distribution upgrade never quietly reverts them. Validate the syntax with
sshd -tbefore reloading, reload, then prove it from a brand-new connection while the current one is still alive. If the new connection fails, you still have a working session to undo it in. One distribution trap: on Ubuntu 24.04 the daemon is socket-activated, soPortinsshd_configis ignored — set the port withsystemctl edit ssh.socketinstead, or disable the socket unit.cat > /etc/ssh/sshd_config.d/10-hardening.conf <<'EOF' Port 2222 PermitRootLogin prohibit-password PasswordAuthentication no KbdInteractiveAuthentication no AllowUsers ops X11Forwarding no MaxAuthTries 3 LoginGraceTime 20 EOF sshd -t && systemctl reload ssh # new terminal, do not close the old one: ssh -p 2222 ops@203.0.113.10
-
04
Default-deny the firewall, on both IP families
Confirm IPv6 is enabled in ufw, then deny everything inbound and open only the ports you are deliberately serving — including the new SSH port, which must be allowed before you enable the firewall. Check what is actually listening while you are here, and move anything that should be local onto loopback.
grep IPV6 /etc/default/ufw # must read IPV6=yes ufw default deny incoming ufw default allow outgoing ufw limit 2222/tcp ufw allow 80,443/tcp ufw enable ufw status verbose ss -tulpen # anything on 0.0.0.0 or :: ?
-
05
Turn on unattended security updates
Enable the security channel only, so patches land without a feature upgrade restarting your application unannounced. Decide the reboot policy explicitly — and leave automatic reboots off if the root filesystem is encrypted and needs a manual unlock.
dpkg-reconfigure -plow unattended-upgrades cat > /etc/apt/apt.conf.d/51-local <<'EOF' Unattended-Upgrade::Automatic-Reboot "false"; Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; EOF unattended-upgrade --dry-run --debug | tail -20
-
06
Trim the noise and tighten the kernel knobs
Add
fail2banfor the layers where a secret can still be guessed, and set the handful of sysctl values that are simply correct on a public server with a static address. This is cheap, and it makes the logs worth reading.apt install -y fail2ban cat > /etc/sysctl.d/99-harden.conf <<'EOF' net.ipv4.tcp_syncookies = 1 net.ipv4.conf.all.rp_filter = 1 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.conf.all.accept_redirects = 0 net.ipv6.conf.all.accept_ra = 0 kernel.kptr_restrict = 2 EOF sysctl --system
-
07
Verify from the outside, then write the runbook
Trust the view from the internet, not the view from inside the box. Confirm the old SSH port is gone, that nothing unexpected answers, and that key-only login is genuinely enforced. Then write down the three facts you will need on your worst day: where your second key lives, which snapshot you took, and where the off-host backup is.
# from another machine ssh -p 22 ops@203.0.113.10 # must time out or be refused ssh -p 2222 -o PreferredAuthentications=password ops@203.0.113.10 # expected: Permission denied (publickey) ssh -p 2222 ops@203.0.113.10 'sudo ufw status verbose; uptime'


