Securing a VPS you just ordered: the first twenty minutes
A VPS here is unmanaged. You get a KVM virtual machine, root access, and no agent watching over your shoulder. That is the point of it, and it means the security of what runs inside is yours.
The good news is that the work is genuinely short. Most of what happens to an internet-facing server is automated and unsophisticated, and three changes stop nearly all of it. This is that list, in the order worth doing it, with the traps that make people think they are done when they are not.
What actually happens to a new server
Within minutes of your IP answering on port 22, you will get login attempts. Not because anyone is interested in you: because the entire IPv4 space is scanned continuously, and a machine that responds gets added to a list. The attempts try root with 123456, then admin with admin, and so on through a dictionary.
That is the threat you are defending against. It is not clever, it is relentless, and it succeeds constantly against servers with password logins and a weak root password.
Almost everything below exists to make that traffic irrelevant rather than to fight it.
Before you change anything, find the console
Do this first, because everything after it can lock you out.
Your VPS control panel has a console that connects to the virtual machine directly rather than over SSH. It is the equivalent of walking up to the machine with a keyboard. Open it once now, log in, and confirm it works.
If you skip this and then make a mistake in the SSH config or the firewall, you have no way back in and the only fix is a reinstall. Every experienced person has done this at least once. Knowing the console works turns a disaster into an annoyance.
1. SSH keys
A key is a very large random secret that never travels to the server. Passwords can be guessed; a properly generated key cannot be, in any timeframe that matters.
On your own machine, not the server:
ssh-keygen -t ed25519 -C "your-laptop"
Ed25519 rather than RSA: shorter, faster and at least as strong. Accept the default path. When it asks for a passphrase, use one. It encrypts the key on your own disk, so a stolen laptop is not automatically a stolen server.
Copy it to the server:
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@your-server-ip
Windows has no ssh-copy-id. From PowerShell:
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh root@your-server-ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Now open a second terminal and confirm ssh root@your-server-ip logs you in without asking for a password. Keep the first one open until it does. Never close your only working session to test a change you just made to how sessions are authenticated.
2. A normal user, and turning password login off
Working as root full time means every mistake is unrecoverable and every compromised process is a compromised machine.
adduser yourname
usermod -aG sudo yourname
rsync --archive --chown=yourname:yourname ~/.ssh /home/yourname
That last line copies your authorised key across so the new account can log in immediately. On RHEL, Rocky and Alma the group is wheel rather than sudo.
Test it from another terminal before continuing: ssh yourname@your-server-ip, then sudo whoami should print root.
Now edit /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
The trap that makes this not work
On Ubuntu, and on most cloud images generally, /etc/ssh/sshd_config begins with a line like:
Include /etc/ssh/sshd_config.d/*.conf
and that directory usually contains 50-cloud-init.conf containing PasswordAuthentication yes.
Here is the part that catches people: in SSH config, the first occurrence of a setting wins, not the last. Because the Include is at the top of the file, anything in that directory overrides what you carefully set further down. You will edit the main file, restart, confirm your edit is still there, and password login will still be enabled.
Check for it:
sudo grep -r PasswordAuthentication /etc/ssh/sshd_config /etc/ssh/sshd_config.d/
If a file in sshd_config.d sets it to yes, edit that file rather than the main one, or remove the offending line. Then verify what the server will actually do, which is the only answer that counts:
sudo sshd -T | grep -E "^(passwordauthentication|permitrootlogin|pubkeyauthentication)"
sshd -T prints the effective configuration after all includes are resolved. If that says passwordauthentication no, you are done. If it says yes, your edit is being overridden somewhere.
Test the config file for syntax errors before restarting, because a broken config means sshd will not come back:
sudo sshd -t && sudo systemctl restart ssh
On Ubuntu 22.10 and later, sshd is started through socket activation (ssh.socket) rather than as a plain service. Restarting ssh still applies config changes, but if you ever change the port, you have to edit the socket unit rather than sshd_config, or the server will keep listening where it did before. This is a common source of confusion; the fix is sudo systemctl edit ssh.socket.
Then, with your existing session still open, prove it in a new one.
3. A firewall
Everything not deliberately published should be unreachable. On Debian and Ubuntu:
sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable
ufw allow OpenSSH before ufw enable, in that order. Reversing them locks you out, which is when you find out whether you checked the console earlier.
Open only what you actually serve:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status verbose
On RHEL-family systems use firewalld instead:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
Two things worth knowing. Databases should not be exposed at all: bind PostgreSQL or MySQL to 127.0.0.1 and reach them over an SSH tunnel. And if you run Docker, be aware that it writes its own iptables rules that bypass ufw, so a published container port is open to the internet even when ufw says it is denied. Bind container ports to localhost (-p 127.0.0.1:8080:80) if they are not meant to be public.
4. Automatic security updates
Unpatched software is how servers get compromised without anyone targeting them. On Debian and Ubuntu:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
By default this installs security updates only, which is the right balance: you get the fixes without a feature update rewriting a config at three in the morning.
On RHEL-family, dnf-automatic does the same job.
Some updates need a reboot to take effect, particularly kernel ones. ls /var/run/reboot-required tells you when one is pending on Debian and Ubuntu.
5. fail2ban, and an honest word about it
fail2ban watches your logs and temporarily bans addresses that fail repeatedly.
sudo apt install fail2ban
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
It is worth installing, but be clear about what it buys you. Once password authentication is off, brute force against SSH cannot succeed anyway. The attempts continue and every one of them fails at the protocol level. fail2ban's real benefit at that point is that your logs stop being 95% noise, which makes the genuinely odd entry visible.
Where it earns its place properly is in front of application logins: a WordPress admin page, a mail server, anything with a password form. Those are the doors that can actually be opened.
Treat it as log hygiene and application protection, not as your SSH defence. Your SSH defence is the key.
Things people do that are not security
Changing the SSH port from 22. This reduces log noise, because most scanners only try 22. It does not stop anyone who runs a port scan first, which is trivial. Do it if the quieter logs help you; do not count it as a control. Note the socket activation point above if you do.
Disabling ping. Your server is discoverable through any open port. Blocking ICMP mostly breaks path MTU discovery and makes your own troubleshooting harder.
Installing a "security suite". On a small VPS these add attack surface and background load in exchange for a dashboard. The three items above cover the overwhelming majority of real risk.
Running everything as root because it is simpler. It is simpler right up to the first mistake.
What is still your job
Backups. The platform backs up the virtual machine. It does not know whether the database inside it was mid-write when the snapshot ran, and it will not help if you delete something and notice in six weeks. Take application-level backups: pg_dump or mysqldump on a schedule, copied somewhere that is not this server. A backup on the machine it protects is not a backup.
Knowing what you are running. sudo ss -tulpn lists every listening socket and the process behind it. Run it now, and again after installing anything. Software that opens a public port without saying so is common, and this is how you find out.
DDoS. There is no application-layer DDoS filtering on VPS here. You get whatever the upstream carrier filters and nothing more. That is stated plainly on the VPS page rather than buried, because finding out during an attack is worse than knowing now. If your workload is a likely target, that matters more than anything else on this page and it is worth saying before you order rather than after.
The short version
Console access confirmed. SSH keys working. Password authentication off and verified with sshd -T, not just edited. Firewall denying by default. Automatic security updates on. Your own backups of anything that matters.
That is twenty minutes and it puts you ahead of most servers on the internet. Everything past it is refinement.
Questions about any of this go to[email protected]. You reach the person who wrote it.