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

#StepKey commandSkip it if
1SSH key at purchasessh-keygen -t ed25519never
2First login, keep it openssh root@IPnever
3Update everythingapt-get update && apt-get -y upgradenever
4Security packagesapt-get -y install unattended-upgrades ufw fail2bannever
5Key-only SSHdrop-in file + sshd -tnever
6Prove passwords failssh -o PubkeyAuthentication=no …never
7Firewallufw allow OpenSSH, ufw --force enablenever
8Reboot if neededsystemctl rebootno reboot-required file
9Dockerapt-get -y install docker.io docker-compose-v2you run no containers
10Domain + HTTPSA record, ufw allow 80/tcp and 443/tcp, Caddyno 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):

CX2315.1CX3312.1CX4310.5CAX1114.0CAX2113.9
docker install s: · x86 ARM

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).

CX23435.0CX33620.0CX43669.0CAX11359.0CAX21529.0
ram used with docker mib: · x86 ARM

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

  1. Ubuntu Discourse, SSHd now uses socket-based activation (2022)
  2. Ubuntu Manpages, sshd_config(5)
  3. Docker Docs, Install Docker Engine on Ubuntu (2026)
  4. Docker Docs, Packet filtering and firewalls (2026)
  5. Caddy Docs, Automatic HTTPS (2026)
  6. Cloudflare Docs, Proxy status (2026)
  7. Debian fail2ban package, jail.d/defaults-debian.conf