How to Secure a New VPS: The First 30 Minutes
A fresh server is scanned within minutes of going online. The essential hardening steps for a new Ubuntu VPS: updates, a non-root user, SSH keys only, firewall, automatic security patches, fail2ban, safe service binding, backups and monitoring — in order, with commands.
The moment a new VPS gets a public IP, bots start trying to log in. Most of them are dumb password-guessers, and a few simple steps make your server uninteresting to them. Do these before you deploy anything.
Commands are for Ubuntu/Debian; other distributions have equivalents.
1. Update everything
apt update && apt full-upgrade -y
reboot # if a new kernel was installed
Fresh images are often weeks behind on security patches.
2. Create a non-root user with sudo
Working as root means every typo has full power, and root is the account every bot tries.
adduser deploy
usermod -aG sudo deploy
3. SSH keys for that user
From your computer (create a key first if you don't have one — SSH keys explained):
ssh-copy-id deploy@YOUR_SERVER_IP
Or, if you only have root access with a key already, copy it across on the server:
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
Test it in a new terminal — ssh deploy@YOUR_SERVER_IP — and confirm sudo works, before the next step.
4. Lock down SSH
Edit /etc/ssh/sshd_config (or add a file in /etc/ssh/sshd_config.d/):
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
Then:
sudo sshd -t && sudo systemctl reload ssh
Keep your current session open and test a fresh login in another terminal. Some cloud images set PasswordAuthentication yes in a separate file under sshd_config.d/ that overrides yours — sudo sshd -T | grep -i password shows the effective value.
Changing the SSH port reduces log noise but isn't real security; keys-only is what matters. (The SSH config file makes connecting with keys painless.)
5. Firewall: deny by default
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable
Add your cloud provider's firewall in front as a second layer. (UFW firewall basics)
6. Automatic security updates
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
This installs security patches daily. Decide whether automatic reboots are acceptable (Unattended-Upgrade::Automatic-Reboot in /etc/apt/apt.conf.d/50unattended-upgrades), or reboot yourself regularly when /var/run/reboot-required exists.
7. Slow down brute-forcers
sudo apt install -y fail2ban
The default config bans IPs after repeated failed SSH logins. With password logins disabled it's mostly about log noise, but it also protects other services you configure it for.
8. Bind services to localhost
Everything that isn't a public website should listen on 127.0.0.1 only:
- Databases (Postgres, MySQL, Redis, MongoDB) — never on
0.0.0.0. Check withsudo ss -ltnp. - Your app on port 3000 — reached only through the reverse proxy on 443.
- Docker published ports — bind to
127.0.0.1:5432:5432, because Docker bypasses UFW. (Container networking internals)
To reach the database from your laptop, use an SSH tunnel. (SSH port forwarding)
9. Run apps as unprivileged users
Your app shouldn't run as root. Run it under deploy (or a dedicated app user) via systemd or PM2, and let the reverse proxy — not your app — bind ports 80/443. (PM2 vs systemd)
Keep secrets in files only that user can read (chmod 600 .env). (Linux file permissions)
10. Backups that leave the server
A backup on the same disk dies with the disk — or with whoever compromises the server. Send database dumps and uploads to object storage or another provider, encrypt them, and test a restore. (Backups for beginners, Postgres backup and restore)
Provider snapshots are a nice extra, not a substitute.
11. Know what's happening
- Uptime and certificate monitoring from outside. (Know when your app is down)
- Disk space alerts — full disks take apps down. (No space left on device)
- Glance at
journalctl -p err -band/var/log/auth.logoccasionally.
The checklist
- Fully updated
- Non-root sudo user
- SSH keys only; root login and passwords off
- Firewall: 22, 80, 443 only
- Automatic security updates
- fail2ban
- Databases and internal ports on localhost
- Apps running as non-root
- Off-server, tested backups
- Uptime, certificate and disk monitoring
The ongoing part
Hardening isn't one-and-done: patch, renew, rotate keys when people leave, review what's listening, and keep the OS within its support window. That ongoing work is the real cost of running your own server. (VPS vs PaaS)
EasySpawn does this for you: managed servers with the OS, patching, firewall, SSL and backups handled, and databases never exposed to the internet — you just deploy. See how it works or join the waitlist.
Related: What Is a VPS? · UFW Firewall Basics · Deploy a Node.js App to a VPS · SSH Keys Explained
Keep reading
Secrets Management Beyond .env Files
.env files are fine on a laptop and fragile everywhere else. Where secrets should live in production and CI, secret managers vs platform env vars, OIDC to remove long-lived CI credentials, rotation, least privilege, keeping secrets out of logs and AI agent context, and a practical maturity path.
Direct-to-Storage Uploads With Presigned URLs
Proxying uploads through your server wastes memory, bandwidth, and request time. How presigned URLs let browsers upload straight to S3, R2, or GCS: PUT vs POST policies, enforcing size and type, bucket CORS, confirming uploads, multipart, and serving private files.