ResourcesΒ·Guides

VPS security checklist: 10 things to do before going to production

A practical, ordered checklist for hardening a fresh Linux VPS. SSH lockdown, firewall, fail2ban, unattended upgrades, log shipping. Skips theory, focuses on commands you can paste.

By RareCloud Team Β· 9 min read Β· 5/20/2026

A fresh VPS is a clean slate, but the internet is hostile from minute one. Your IPv4 will see SSH brute-force attempts within hours of provisioning. None of these steps are exotic, most are 10 minutes of work. Do them once per server, save yourself an incident.

Run them in order. Each step is independent but builds on the previous one.

1. Create a non-root sudo user

Logging in as root is fine for the first 10 minutes; after that, switch.

$ adduser deploy                          # interactive, set a strong password
$ usermod -aG sudo deploy
$ mkdir -p /home/deploy/.ssh
$ cp /root/.ssh/authorized_keys /home/deploy/.ssh/
$ chown -R deploy:deploy /home/deploy/.ssh
$ chmod 700 /home/deploy/.ssh
$ chmod 600 /home/deploy/.ssh/authorized_keys

Test SSH as deploy from another terminal before continuing, don't lock yourself out.

$ ssh deploy@your-server
deploy@server:~$ sudo whoami
root

2. Disable root SSH and password authentication

Edit /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no

Reload:

$ sudo systemctl reload ssh

Try logging in as root, should fail. SSH from a wrong machine, should fail. Now password-based brute force is impossible regardless of password strength.

3. Change the SSH port (optional, low value)

Port 22922 (or any high port). Reduces the log noise from internet scanners; doesn't add real security because anyone serious will port-scan first. Worth it only if you hate seeing thousands of failed-login lines in your auth log.

4. Set up UFW (firewall)

UFW is a sane wrapper around iptables. Default: deny everything in, allow everything out.

$ sudo apt install -y ufw
$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing
$ sudo ufw allow 22922/tcp           # or whatever your SSH port is
$ sudo ufw allow 80/tcp
$ sudo ufw allow 443/tcp
$ sudo ufw enable

Verify:

$ sudo ufw status
Status: active
To                         Action      From
--                         ------      ----
22922/tcp                  ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere

For each service you run, open the port explicitly. Don't ufw allow from any to any, that's the same as no firewall.

5. Enable unattended-upgrades

$ sudo apt install -y unattended-upgrades
$ sudo dpkg-reconfigure --priority=low unattended-upgrades

This patches security updates daily. Default config is reasonable; if you want to also reboot automatically when a kernel update needs it, edit /etc/apt/apt.conf.d/50unattended-upgrades:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

For critical workloads, prefer manual reboots. For most apps, automatic at 4 AM is fine.

6. Install fail2ban

$ sudo apt install -y fail2ban
$ sudo systemctl enable --now fail2ban

Default config bans IPs after 5 failed SSH attempts for 10 minutes. Adjust in /etc/fail2ban/jail.local:

[sshd]
enabled = true
maxretry = 3
bantime = 1h
findtime = 10m

Check banned IPs:

$ sudo fail2ban-client status sshd

7. Set up swap (if your VPS doesn't have any)

OOM kills are a common cause of mystery downtime. Add 2 GB swap on a 1-4 GB RAM VPS:

$ 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

Tune swappiness (prefer RAM, use swap only when really needed):

$ sudo sysctl vm.swappiness=10
$ echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf

8. Configure a separate audit log destination (recommended)

/var/log/auth.log records every SSH attempt, sudo use, and login. If an attacker gets in, the first thing they do is wipe logs. Ship them to somewhere they can't reach.

Simple version, rotate aggressively and back up offsite:

$ sudo nano /etc/logrotate.d/auth-extra
/var/log/auth.log {
    daily
    rotate 30
    compress
    delaycompress
    notifempty
    postrotate
        rsync /var/log/auth.log.1.gz backup-host:/path/auth-logs/$(hostname)-$(date +%Y%m%d).gz
    endscript
}

Production version: ship to Loki / CloudWatch / Datadog. Pick whatever you already have.

9. Set up basic monitoring + alerting

Even a simple "is the server up" check beats no monitoring:

  • Healthchecks.io for dead-man-switch crons (free tier is generous).
  • UptimeRobot for HTTP endpoint checks.
  • Self-hosted Prometheus + Grafana if you want time-series metrics.

Wire whatever alerts you set up to a channel you actually read. SMS, Telegram, PagerDuty. An email alert to an inbox you check once a day isn't monitoring.

10. Take a baseline snapshot

# Via dashboard or:
$ rarecloud server backup create $SRV

This is your clean-state restore point. If you ever suspect a compromise, you can spin up a fresh server from this snapshot and migrate data over, instead of debugging a possibly-rooted machine.

What this does and doesn't protect against

This checklist protects against:

  • Brute-force SSH attempts (steps 2, 6)
  • Unknown vulnerabilities in unpatched packages (step 5)
  • Casual port-scanning attempts (step 4)
  • Out-of-memory crashes (step 7)
  • Total data loss from a single bad change (step 10)

It does not protect against:

  • Vulnerabilities in your application code itself
  • Stolen API tokens or secrets in your repo
  • A compromised SSH key on your laptop (see SSH key management)
  • Supply-chain attacks via npm / pip dependencies

For application-layer security, that's a different doc. This one gets you to a baseline-hardened OS.

Frequently Asked Questions

How often should I patch?
Unattended-upgrades handles security patches daily without manual intervention. For non-security packages (which can occasionally break things), once a month is reasonable for most workloads. For internet-facing services, run apt full-upgrade after announcement of any critical CVE affecting your stack.
Is fail2ban still needed if I've disabled password auth?
Less critical, but still useful. It can ban IPs that hammer your sshd port (even unsuccessfully), reducing log noise. It also protects other services, web apps, mail servers, from credential-stuffing. Once you have key-only SSH, fail2ban is more about log hygiene than primary defense.
Do I need a WAF on a small VPS?
Not for the average self-hosted app. A WAF (web application firewall) is worth setting up if you're handling untrusted user input, accepting file uploads, or running off-the-shelf PHP apps with patchy security history. For a single Node app you wrote, fewer dependencies = less attack surface; focus on dependency updates instead.

Related