The first hour on a fresh VPS
A new server starts accepting login attempts from the internet within minutes of booting. This is what we do in the first hour on a fresh Ubuntu VPS, step by step, with the commands.
3 min readBy Alif Hossain
A fresh VPS is reachable from the whole internet the moment it boots, and automated scanners find new servers quickly. Watch the SSH log of a brand new machine for an hour and you will usually see strangers trying passwords.
Nothing here is advanced. It is the short list that makes a server a much less attractive target before any real work goes on it. The commands are for Ubuntu 24.04. Most apply to any Debian-based system.
Keep your current SSH session open until you have confirmed a new one works. Locking yourself out of a server is the most common mistake in this process.
1. Update everything
sudo apt update && sudo apt full-upgrade -y
sudo reboot
Start from current packages. Reboot if the kernel was updated.
2. Create your own user
Do not work as root day to day. Create a user with sudo rights:
adduser deploy
usermod -aG sudo deploy
3. Log in with a key, not a password
On your own computer, create a key if you do not have one, then copy it to the server:
ssh-keygen -t ed25519 -C "you@yourcompany.com"
ssh-copy-id deploy@your-server-ip
Open a new terminal and check that ssh deploy@your-server-ip works without a password prompt before going further.
4. Turn off password and root login
Edit the SSH configuration:
sudo nano /etc/ssh/sshd_config
Set these two lines:
PasswordAuthentication no
PermitRootLogin no
On many cloud images, a file in /etc/ssh/sshd_config.d/ sets PasswordAuthentication yes and quietly overrides the main file. Check that folder and fix it there too:
sudo grep -r PasswordAuthentication /etc/ssh/sshd_config.d/
Then restart SSH and test again from a new terminal:
sudo systemctl restart ssh
5. Turn on the firewall
Allow SSH and web traffic, and nothing else:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
One trap: Docker bypasses ufw. A container published with -p 5432:5432 is reachable from the internet even if ufw says the port is closed. Bind private services to localhost instead, for example -p 127.0.0.1:5432:5432, and put only your reverse proxy on ports 80 and 443.
6. Install security updates automatically
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Security patches now install on their own. You still need to reboot for kernel updates, so check in on the server now and then.
7. Slow down login attacks
sudo apt install fail2ban
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Fail2ban bans addresses that keep failing to log in. With password login already off, this is a second layer rather than the main defence, but it keeps the logs readable.
8. Serve the app over HTTPS
Run your application on a local port and put a reverse proxy in front of it. Caddy gets and renews TLS certificates automatically:
sudo apt install caddy
Then set /etc/caddy/Caddyfile to:
yourdomain.com {
reverse_proxy 127.0.0.1:3000
}
sudo systemctl reload caddy
Point your domain's DNS at the server, and the site is live on HTTPS. The app itself never listens on a public port.
9. Back up, and test the backup
Two layers, because each covers a different failure:
- Provider snapshots of the whole server, if your host offers them, to recover from a broken system.
- Off-server copies of your data, such as a nightly database dump sent to object storage in another location, to recover from deleted or corrupted data.
# nightly Postgres dump, run from cron
pg_dump "$DATABASE_URL" | gzip > /var/backups/db-$(date +%F).sql.gz
Then restore one into a scratch database. Until you have restored a backup, you do not know you have one.
10. Know when it goes down
Set up an external uptime check that hits your site every minute or two and alerts you by email or chat when it fails. An outage you hear about from a customer has already cost you.
What you have after an hour
- Logins only by key, and no root login.
- Only ports 22, 80, and 443 open.
- Security patches installing themselves.
- Repeated login failures banned.
- The app behind HTTPS with certificates that renew themselves.
- Backups that you have actually restored, and an alert when the site is down.
It is not everything a production server needs, but it closes the doors that automated attacks try first. If you would rather someone else set it up and look after it, server administration is one of the things we do.

