A good VPS setup checklist has ten steps: add an SSH key, log in, update, install security tools, switch SSH to keys only, prove passwords are refused, enable the firewall, reboot, install Docker, then point a domain at it with HTTPS. We ran exactly these on our production Hetzner CX23 with Ubuntu 26.04 on 2026-11-05. The slowest command, the Docker install, took 22 s.
This matters now because Ubuntu 24.04 and 26.04 changed how SSH restarts, and many checklists still tell you to run a command that fails. Below, each step has the exact command and what we saw. It is part of our how to start with a VPS section.
Tested in the Lab Hetzner CX23 (production, Ubuntu 26.04); CX23, CX33, CX43, CAX11, CAX21 (Ubuntu 24.04) · Nov 2026
The checklist at a glance
| # | Step | Key command | Skip it if |
|---|---|---|---|
| 1 | SSH key at purchase | ssh-keygen -t ed25519 | never |
| 2 | First login, keep it open | ssh root@IP | never |
| 3 | Update everything | apt-get update && apt-get -y upgrade | never |
| 4 | Security packages | apt-get -y install unattended-upgrades ufw fail2ban | never |
| 5 | Key-only SSH | drop-in file + sshd -t | never |
| 6 | Prove passwords fail | ssh -o PubkeyAuthentication=no … | never |
| 7 | Firewall | ufw allow OpenSSH, ufw --force enable | never |
| 8 | Reboot if needed | systemctl reboot | no reboot-required file |
| 9 | Docker | apt-get -y install docker.io docker-compose-v2 | you run no containers |
| 10 | Domain + HTTPS | A record, ufw allow 80/tcp and 443/tcp, Caddy | no web app |
1. Add an SSH key when you buy the server
Create the key on your own computer and paste the public half into the provider's "SSH key" field during checkout. The server then starts with key login and no root password is ever mailed to you.
ssh-keygen -t ed25519 -C "my-vps"
cat ~/.ssh/id_ed25519.pub
Pick the size with your workload in mind, not the cheapest price. The VPS RAM calculator uses our measurements to estimate it.
2. Log in and keep that session open
ssh root@YOUR_SERVER_IP
This first session is your lifeline. Leave it open until step 6 passes. If you break SSH, you can still repair it from here or from the provider's web console.
3. Update everything
export DEBIAN_FRONTEND=noninteractive
apt-get update && apt-get -y upgrade
Cloud images are often weeks behind. On our server this step pulled a new kernel and libraries.
A fresh Ubuntu 24.04 install is small. Before we installed anything, it used 444 MiB of RAM on the Hetzner CX23 and 308 MiB on the ARM-based CAX11 (MiB = mebibyte, 1,048,576 bytes). The rest is yours for apps.
4. Install the security basics
apt-get -y install unattended-upgrades ufw fail2ban
Automatic security updates, a simple firewall, and a tool that bans addresses after repeated login failures. On Ubuntu, fail2ban's package already enables the SSH jail.
5. Switch SSH to keys only
cat > /etc/ssh/sshd_config.d/10-lab-hardening.conf <<CONF
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
CONF
sshd -t
systemctl reload ssh
The gotcha: on our Ubuntu 26.04 server the last line failed with ssh.service is not active. Since Ubuntu 22.10, sshd is started per connection through ssh.socket, so often no service is running to reload. Nothing is wrong: each new connection reads the new file. sshd -t is the line that matters; it prints nothing when the config is valid.
6. Prove that password login is refused
From your own computer, in a second terminal:
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no root@YOUR_SERVER_IP
Our server answered Permission denied (publickey). without asking for a password. Then confirm your normal key login still works. Only now close the first session.
7. Turn on the firewall
ufw allow OpenSSH
ufw --force enable
ufw status verbose
Allow SSH before enabling, or you cut your own connection. Leave 80 and 443 closed until step 10 if you do not serve web traffic yet. The full script, with every line explained, is in how to secure an Ubuntu VPS.
8. Reboot if the system asks for it
[ -f /var/run/reboot-required ] && systemctl reboot
A new kernel only takes effect after a restart. We rebooted here, before installing Docker, so later steps ran on the patched system. Log back in with your key afterwards.
9. Install Docker
apt-get -y install docker.io docker-compose-v2
systemctl enable --now docker
On our production CX23 (Ubuntu 26.04) this took 22 s. On our five test servers with Ubuntu 24.04 it took between 10.5 s (CX43) and 15.1 s (CX23):
These are Ubuntu's packages. Docker's own docs call them unofficial and offer a docker-ce repository with newer versions; either works for a small stack. One rule matters more than the package: Docker's published ports skip ufw. Write 127.0.0.1:5678:5678, not 5678:5678, and put a reverse proxy in front. Our n8n install on Hetzner shows a full compose file built that way.
10. Point a domain at it and add HTTPS
Create an A record for a subdomain with your server's IP. If your DNS is on Cloudflare, set the record to DNS only: a proxied record answers with Cloudflare's IPs, and the certificate check then reaches Cloudflare instead of your server. Check it, then open the web ports and install Caddy:
dig +short app.example.com # must print your server IP
ufw allow 80/tcp
ufw allow 443/tcp
apt-get -y install caddy
Caddy gets a Let's Encrypt certificate by itself once the name resolves and ports 80 and 443 are reachable. The Caddyfile we run and the checks we used are in n8n behind Caddy with HTTPS.
One more thing on the same day: many self-hosted apps show a first-admin setup page until someone creates an account. n8n does. Create that account the moment the domain goes live, before anyone else finds it.
How much server is left after the checklist
The setup itself costs little RAM. With Docker installed and idle, our test servers on Ubuntu 24.04 used between 359 MiB (CAX11) and 669 MiB (CX43).
So on a 4 GB plan such as the CX23, 3,819 MiB total minus 435 MiB used leaves about 3,384 MiB when you start installing apps. What you put on top decides the plan: our n8n with Postgres used 1,036 MiB for the whole server idle on the production CX23.
Why the order matters
Every step protects the next one. These are the order mistakes that lock people out or leave a gap:
- Enabling ufw before allowing SSH. Your session drops and the server refuses new SSH connections. Step 7 allows OpenSSH first for this reason.
- Turning off passwords before testing the key. If the key is wrong, there is no way back in except the web console. That is why steps 1 and 2 come before step 5.
- Closing the first session before step 6. The open session is your undo button. Close it only after a fresh key login works and a password login fails.
- Installing apps before the reboot. Containers started on the old kernel restart anyway when you reboot later, usually at a worse moment.
- Opening 80 and 443 before an app listens there. Nothing is exposed, but you lose track of why a port is open. Open them together with Caddy in step 10.
- Exposing an app before claiming its admin account. The first visitor to a fresh n8n becomes its owner.
What is not on our list yet
- Backups. Set them up before you store data you care about. We have not run and timed a restore in the Lab yet, so we are not giving you an untested recipe.
- A non-root user. We log in as root with a key only (
prohibit-password). On a server several people share, give each person their own user. - A different SSH port. We kept port 22. With passwords off, it only reduces log noise.
Next step: understand every hardening line before you rely on it in how to secure an Ubuntu VPS.
FAQ
What is the first thing to do after buying a VPS?
Log in with an SSH key, keep that session open, and update all packages. Then turn off password login before you install any apps.
How long does a basic VPS setup take?
The commands themselves are fast: Docker installed in 22 seconds on our production Hetzner CX23. Most of the time goes into reading, testing the SSH login and waiting for DNS.
Do I need a firewall if my provider has one?
A host firewall such as ufw is still worth it because it travels with the server. Remember that Docker's published ports skip ufw, so bind containers to 127.0.0.1.
Should I reboot after the first update?
Yes, if /var/run/reboot-required exists. On our server the first upgrade pulled a new kernel and libraries, so we rebooted before installing Docker.
Where do backups fit in this checklist?
Right after step 10, before you store anything important. We have not tested a backup and restore flow in the Lab yet, so it is not one of our ten measured steps.
Sources
- Ubuntu Discourse, SSHd now uses socket-based activation (2022)
- Ubuntu Manpages, sshd_config(5)
- Docker Docs, Install Docker Engine on Ubuntu (2026)
- Docker Docs, Packet filtering and firewalls (2026)
- Caddy Docs, Automatic HTTPS (2026)
- Cloudflare Docs, Proxy status (2026)
- Debian fail2ban package, jail.d/defaults-debian.conf
