How to Secure a New Linux VPS: 10 Steps for the First Hour
A practical hardening checklist for a fresh Ubuntu or Debian server: a sudo user, SSH keys, firewall, automatic updates, fail2ban and backups.
· 6 min read
Contents
- Before you start
- Step 1: Update the system
- Step 2: Create a regular user with sudo
- Step 3: Set up SSH key login for the new user
- Step 4: Lock down SSH
- Should you change the SSH port?
- Step 5: Turn on the firewall
- Step 6: Enable automatic security updates
- Step 7: Install fail2ban
- Step 8: Set the time zone and hostname
- Step 9: Add swap on small servers
- Step 10: Plan backups before you need them
- Quick checklist
- What to do next
- FAQ
- Is a VPS secure by default?
- Do I need fail2ban if I only allow SSH keys?
- I locked myself out. What now?
- Further reading
Bots start probing a new server within minutes of it getting a public IP address. Most of them try default passwords over SSH, so a few basic steps stop nearly all automated attacks.
This checklist is written for Ubuntu 22.04/24.04 and Debian 12, the most common VPS images. It takes about an hour, and you only do it once per server.
Before you start
You need the server's IP address and the root password or SSH key your provider gave you. Throughout this guide, replace 203.0.113.10 with your IP and deploy with the username you want.
Golden rule: whenever you change SSH or the firewall, keep your current session open and test the change from a second terminal. If something is wrong, you can still fix it from the first one. Most providers also offer a web console as a last resort.
Step 1: Update the system
ssh root@203.0.113.10
apt update && apt upgrade -y
If the upgrade installs a new kernel, reboot with reboot and log in again.
Step 2: Create a regular user with sudo
Working as root all the time makes every mistake catastrophic. Create a normal user:
adduser deploy
usermod -aG sudo deploy
Choose a strong password — you will use it for sudo, not for logging in.
Step 3: Set up SSH key login for the new user
From your own computer (not the server), copy your public key:
ssh-copy-id deploy@203.0.113.10
No SSH key yet? Our SSH keys guide walks through creating one on Linux, macOS and Windows.
Test it in a new terminal:
ssh deploy@203.0.113.10
sudo whoami # should print: root
Step 4: Lock down SSH
Disable password logins and direct root login. Rather than editing the main file, create a drop-in file — it survives package updates:
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
MaxAuthTries 3
Watch out for other drop-in files. Many cloud images ship a file such as /etc/ssh/sshd_config.d/50-cloud-init.conf containing PasswordAuthentication yes. SSH uses the first value it reads for most settings, and files are read in alphabetical order, so that file can override yours. Check:
grep -r PasswordAuthentication /etc/ssh/sshd_config /etc/ssh/sshd_config.d/
Remove or edit any conflicting line, then validate the config and reload:
sudo sshd -t && sudo systemctl reload ssh
Confirm the effective settings:
sudo sshd -T | grep -Ei 'passwordauthentication|permitrootlogin'
From a second terminal, check that ssh deploy@203.0.113.10 still works and that ssh root@203.0.113.10 is refused.
Should you change the SSH port?
Moving SSH off port 22 reduces log noise from bots, but it is not real security — key-only authentication is. If you do change it, remember that recent Ubuntu versions start SSH through systemd socket activation, so a Port change in the config also needs sudo systemctl daemon-reload && sudo systemctl restart ssh.socket. Open the new port in the firewall before switching.
Step 5: Turn on the firewall
Ubuntu includes ufw (Uncomplicated Firewall). On Debian, install it with sudo apt install ufw.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH # or: sudo ufw allow 22/tcp
sudo ufw allow 80,443/tcp # only if this server will host websites
sudo ufw enable
sudo ufw status verbose
Allow SSH before enabling the firewall, or you will lock yourself out.
If you use Docker: ports published with -p or a Compose ports: section are added to iptables directly and can bypass ufw rules. Publish only what must be public, and bind internal services to localhost, for example "127.0.0.1:5432:5432". More in our Docker Compose guide.
Step 6: Enable automatic security updates
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Choose "Yes". Security patches are now installed daily. To reboot automatically at a quiet time when an update requires it, edit /etc/apt/apt.conf.d/50unattended-upgrades and set:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Step 7: Install fail2ban
fail2ban watches logs and temporarily bans IPs that fail to log in repeatedly. With password logins disabled it is less critical, but it cuts noise and protects other services you add later.
sudo apt install fail2ban
sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1h
On Debian 12, which logs to the systemd journal instead of /var/log/auth.log, also add backend = systemd under [sshd].
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Step 8: Set the time zone and hostname
Logs are easier to read with a sensible time zone. Many admins keep servers on UTC so scheduled jobs are not affected by daylight saving time (see cron and time zones).
sudo timedatectl set-timezone UTC
sudo hostnamectl set-hostname web-01
Step 9: Add swap on small servers
A 1 GB VPS can run out of memory during package installs or Docker builds. A swap file gives you breathing room:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h
Step 10: Plan backups before you need them
Security also means being able to recover. At minimum:
- Enable your provider's snapshots or backups for the whole server. It usually costs a small percentage of the server price.
- Back up application data separately (database dumps, uploaded files) to a different provider or location.
- Test a restore once. A backup you have never restored is a hope, not a backup.
Quick checklist
| Done | Step |
|---|---|
| ☐ | System updated |
| ☐ | Non-root sudo user created |
| ☐ | SSH key login works for that user |
| ☐ | Password and root SSH logins disabled (checked with sshd -T) |
| ☐ | ufw enabled with only needed ports open |
| ☐ | Automatic security updates enabled |
| ☐ | fail2ban running |
| ☐ | Time zone and hostname set |
| ☐ | Swap configured (small servers) |
| ☐ | Backups enabled and a restore tested |
What to do next
- Put a reverse proxy with automatic HTTPS in front of your apps — see how to set up Caddy as a reverse proxy.
- Check what is listening on the network with
sudo ss -tulpn. Anything you do not recognize deserves a look. - Review logins occasionally with
lastandsudo journalctl -u ssh --since today.
FAQ
Is a VPS secure by default?
Not fully. Providers give you a working system, but hardening — SSH keys, firewall, updates — is your responsibility.
Do I need fail2ban if I only allow SSH keys?
Bots cannot get in with password guessing once passwords are disabled, so it is optional for SSH. It still reduces log noise and helps protect other services.
I locked myself out. What now?
Use your provider's web or recovery console, which works even when SSH does not. From there, revert the last SSH or firewall change.
Further reading
Keep reading
Self-Hosting on a Raspberry Pi: What Works and How to Set It Up
What a Raspberry Pi can realistically self-host, the hardware that makes it reliable, a step-by-step Docker setup and how to reach it securely from anywhere.
Docker Compose for Beginners: Self-Host Your First App
Learn Docker Compose step by step: install it, write your first compose.yaml, persist data with volumes, connect services and keep everything updated.
How to Back Up and Restore Docker Volumes the Right Way
Back up Docker named volumes and bind mounts safely, dump databases consistently, automate it with cron, copy backups off-site and test restores.