I run most of my self-hosted stack on bare-metal VPS instances — things like Nextcloud, a personal Gitea, and a few smaller apps. Every time I spin up a fresh box on Hetzner or Vultr , I work through the same checklist before I touch anything else. This is that checklist, updated for 2026.
The commands below assume Ubuntu 24.04 LTS but work on any recent Debian-based distro with minor adjustments.
1. Create a Non-Root Sudo User
Every provider gives you root access by default. The first thing to do is make a normal user account and never log in as root again.
adduser deploy
usermod -aG sudo deploy
Log out, then test that deploy can use sudo before you close the root session.
2. Switch to SSH Key Authentication
Password authentication is the main attack surface bots target. Generate a key pair on your local machine if you do not already have one:
ssh-keygen -t ed25519 -C "[email protected]"
Copy your public key to the server:
ssh-copy-id deploy@YOUR_SERVER_IP
Test key login, then open /etc/ssh/sshd_config and set:
PasswordAuthentication no
PubkeyAuthentication yes
Restart SSH:
sudo systemctl restart ssh
3. Restrict SSH Access
While you are in sshd_config, disable direct root SSH login:
PermitRootLogin no
Optionally move SSH off port 22. This does not stop a determined attacker but it eliminates the constant noise of automated scanners in your logs:
Port 2222
If you change the port, remember to allow it in the firewall before you restart SSH, or you will lock yourself out.
4. Set Up UFW Firewall
UFW is the easiest firewall manager on Ubuntu. Start with a default-deny policy, then allow only what you need:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH # adjust if you changed the port: sudo ufw allow 2222/tcp
sudo ufw enable
sudo ufw status verbose
Add ports for any services you run — for example, sudo ufw allow 80/tcp and sudo ufw allow 443/tcp for a web server. See my Nginx Proxy Manager setup guide for a typical web-serving configuration.
5. Install fail2ban
Even with SSH keys, bots still hammer every port they can find. fail2ban reads logs and temporarily bans IPs that trigger too many failures:
sudo apt install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local to set reasonable defaults:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
Enable and start the service:
sudo systemctl enable --now fail2ban
sudo fail2ban-client status
6. Enable Automatic Security Updates
Keeping packages patched is one of the most effective things you can do. On Ubuntu, unattended-upgrades handles this:
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
To verify it is active:
sudo systemctl status unattended-upgrades
This only auto-installs security updates by default, which is the safe setting — feature updates still require manual intervention.
7. Disable Unused Services
Every running service is an attack surface. List what is listening:
sudo ss -tulnp
Stop and disable anything you do not recognize or need:
sudo systemctl stop avahi-daemon
sudo systemctl disable avahi-daemon
Common candidates on a fresh Ubuntu install: avahi-daemon, cups, ModemManager. Check before disabling — some services have less obvious dependencies.
8. Set Up a Basic Backup
A compromised server is recoverable if you have a recent backup. At minimum, automate a daily snapshot of your important directories to an off-server location. A simple approach with rsync to a remote host:
rsync -avz --delete /var/www/ backup-user@BACKUP_HOST:/backups/www/
For a fuller self-hosting backup strategy, the self-hosting Nextcloud guide covers setting up Nextcloud as a backup target alongside object storage.
9. Enable BBR and Keep the Kernel Updated
BBR (Bottleneck Bandwidth and RTT) is Google’s congestion-control algorithm. On modern Ubuntu kernels it is available and gives noticeably better throughput for web traffic. Check if it is active:
sysctl net.ipv4.tcp_congestion_control
If it shows cubic, switch to BBR by adding these lines to /etc/sysctl.conf:
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
Apply with sudo sysctl -p. For kernel updates, unattended-upgrades handles security patches, but full kernel updates still require a reboot. Schedule a monthly maintenance window or use Ubuntu Pro’s Livepatch for zero-downtime kernel updates.
10. Monitoring and Log Basics
You cannot respond to problems you cannot see. At minimum, set up:
Log rotation — Ubuntu ships with logrotate already configured; verify it is running:
sudo logrotate --debug /etc/logrotate.conf
Basic resource monitoring — htop for interactive sessions, and consider netdata for a lightweight real-time dashboard you can self-host.
Uptime alerts — a free UptimeRobot account pings your server every few minutes and emails you if it goes down. No configuration needed on the server side.
Once your server is hardened, the next step is getting traffic to it. Check the guide on pointing a domain to your VPS to set up DNS properly before you expose any services publicly.
These ten steps take about 20 minutes on a fresh box and cover the vast majority of what automated attacks target. Bookmark this page — I update it whenever a step needs adjusting for a new LTS release.