# Secure VPS setup: a first-hour checklist for Ubuntu and Debian

URL: https://cloudj.net/blog/secure-new-vps-checklist
Published: 2026-11-11 · Updated: 2026-11-11

> Secure a VPS in its first hour on Ubuntu or Debian: updates, a sudo user, SSH keys only, a firewall, fail2ban and automatic security updates, step by step.

A secure VPS is built in the first hour after you deploy it, before you install anything else. Internet-facing servers start receiving automated SSH login attempts within minutes of getting a public IP, and every service you add later is easier to protect on a hardened base. This checklist takes a fresh Ubuntu or Debian server through the essentials in order: updates, a non-root user, key-only SSH, a firewall, fail2ban and automatic security updates.

It assumes you deployed with an SSH key. If you have not created one yet, follow [how to create SSH keys and log in to your VPS](https://cloudj.net/blog/ssh-key-vps-setup) first. For choosing the region itself, see the [VPS locations guide](https://cloudj.net/blog/vps-locations-guide).

## Before you start: keep a way back in

Hardening SSH is the one step that can lock you out. Two habits prevent that:

- Keep your current SSH session open while you change SSH settings, and test with a second terminal before closing the first.
- Know your fallback. On Cloudj servers you can open an SSH console in the browser from the server page in the [console](https://app.cloudj.net), and you can reset the root password there if needed.

Commands below are run as root on first login, and with `sudo` after you create your own user. Replace `deploy` with the username you want.

## Minute 0 to 10: update the system

Install current packages before anything else, so you are not hardening a system with known holes.

```bash
apt update
apt full-upgrade -y
```

If the upgrade installed a new kernel, reboot now while nothing important is running. On Ubuntu the file `/run/reboot-required` exists when a reboot is needed; on Debian, reboot whenever the upgrade included a `linux-image` package.

```bash
ls /run/reboot-required
reboot
```

While you wait, check the hostname and time zone. Correct time matters for logs and for TLS:

```bash
timedatectl
```

## Minute 10 to 20: create a sudo user

Working as root all day turns every typo into a system-wide mistake. Create a normal user with `sudo` rights:

```bash
adduser deploy
usermod -aG sudo deploy
```

Copy root's authorized SSH keys to the new user so you can log in with the same key:

```bash
install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
install -m 600 -o deploy -g deploy ~/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
```

Now open a second terminal on your computer and confirm the new login works, including `sudo`:

```bash
ssh deploy@203.0.113.10
sudo whoami
```

The last command should print `root`. Do not continue until this works.

## Minute 20 to 30: secure VPS SSH access

With key login confirmed for your new user, turn off password logins and direct root login. Use a drop-in file instead of editing the main config, so package upgrades do not overwrite your change.

One detail matters here: OpenSSH uses the first value it reads for each setting, and drop-in files are read in alphabetical order. Some cloud images ship a file such as `50-cloud-init.conf` that turns password login on, so name yours to sort first:

```bash
sudo tee /etc/ssh/sshd_config.d/00-hardening.conf > /dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
EOF
```

Check the syntax, confirm the effective values, then restart SSH:

```bash
sudo sshd -t
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauthentication|kbdinteractive'
sudo systemctl restart ssh
```

The service is called `ssh` on both Ubuntu and Debian. Keep your existing session open and test a fresh login from another terminal. Then confirm that root is refused:

```bash
ssh root@203.0.113.10
```

You should see `Permission denied (publickey)`. That is the result you want.

Changing the SSH port away from 22 is optional. It reduces log noise but adds little real security once passwords are off; if you change it, open the new port in the firewall before restarting SSH.

## Minute 30 to 40: turn on a firewall

`ufw` is the simplest host firewall on Ubuntu and Debian. Ubuntu usually has it installed; on Debian, install it first. Allow SSH before enabling, or you will cut your own session:

```bash
sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status verbose
```

Open only what the server actually serves. For a web server that usually means:

```bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
```

Databases, admin panels and caches should not be open to the internet. Bind them to `127.0.0.1` or allow them only from specific IPs. Our [ufw firewall guide](https://cloudj.net/blog/ufw-firewall-vps) goes deeper into writing rules. If you plan to run containers, read the firewall section in [installing Docker on a VPS](https://cloudj.net/blog/install-docker-vps), because Docker's published ports bypass ufw rules by default.

## Minute 40 to 50: block repeated login attempts with fail2ban

With passwords off, brute force cannot succeed, but fail2ban still cuts log noise and slows scanners by banning IPs that fail repeatedly.

```bash
sudo apt install fail2ban python3-systemd
```

Create a local jail file. Using the systemd backend works on both Ubuntu and newer Debian releases, where SSH logs may live only in the journal:

```bash
sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF'
[sshd]
enabled = true
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
EOF
sudo systemctl enable --now fail2ban
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
```

If you lock yourself out from your own IP by mistake, use the browser console to unban it with `sudo fail2ban-client set sshd unbanip YOUR_IP`.

## Minute 50 to 60: automatic security updates

Security fixes only help when they are installed. `unattended-upgrades` applies them daily:

```bash
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
```

Answer "Yes" to enable automatic updates. By default it installs security updates only, which is the safe choice for most servers. You can check what it did in `/var/log/unattended-upgrades/`.

## The checklist at a glance

| Step | Command or setting | Done when |
|---|---|---|
| Update | `apt full-upgrade` | No pending updates, rebooted if needed |
| Sudo user | `adduser`, `usermod -aG sudo` | `sudo whoami` prints `root` |
| SSH hardening | `00-hardening.conf` | Root and password logins refused |
| Firewall | `ufw` with SSH allowed | `ufw status` shows only needed ports |
| fail2ban | `[sshd]` jail, systemd backend | `fail2ban-client status sshd` works |
| Auto updates | `unattended-upgrades` | Enabled in `dpkg-reconfigure` |

## After the first hour

Security does not end here. Plan backups before you have data worth losing; the [VPS backup strategy guide](https://cloudj.net/blog/vps-backup-strategy) covers options. Keep an eye on CPU, RAM and network use in the console metrics so unusual activity stands out. And if you run a web app, put it behind a reverse proxy with TLS rather than exposing application ports directly.

## FAQ

### Is a firewall needed if only SSH is running?

It is still worth it. A default-deny firewall protects you when you later install a service that listens on all interfaces without you noticing.

### Should I disable root login if I use SSH keys?

Yes. A named sudo user leaves a clearer audit trail, and refusing root removes the most targeted username from the attack surface.

### What if I lock myself out?

Use the browser SSH console on the server page in the Cloudj console to log in and fix the configuration, or reset the root password there.

### Does this apply to Windows servers?

No. Windows servers on Cloudj use password login and need Windows-specific hardening such as strong passwords, Windows Update and restricting RDP access.

## Start on a clean server

Deploy a fresh Ubuntu or Debian server in the [Cloudj console](https://app.cloudj.net) with your SSH key attached, and work through this list before anything else. You can compare locations on the [cloud servers page](https://cloudj.net/cloud-servers) and see live prices on the [pricing page](https://cloudj.net/pricing).
