NSD
nonstop development
Back to notebook
TutorialSecurity

VPS Security Checklist for Indie Hackers (The 30-Minute Hardening Pass)

The moment you put a VPS on the internet, bots start knocking. Here is the no-drama checklist I run before I deploy anything real.

February 21, 20267 min read
Share:

VPS Security Checklist for Indie Hackers (The 30-Minute Hardening Pass)

Featured Image

The moment you put a VPS on the internet, it starts getting scanned.

No DNS. No announcement. No traffic. Still scanned.

If you're using a VPS because it's fast and simple (same), here's the minimum hardening pass that keeps you out of the "I got owned overnight" category.

Sponsored plug: For quick VPS servers, Vultr is my go-to. Their shared CPU plans start at $5/month and can scale as you need to.

This is extra relevant with the self-hosting wave (OpenClaw and friends). If your box is holding API keys, tokens, and access to your stuff, treat it like a wallet.

Kubernetes Brain, VPS Reality

Kubernetes is great. We run a cluster. Containerized workflows are real power.

But a lot of people bounce off it because:

  • There is a learning curve before you ship anything
  • You have more moving parts than a single server needs
  • You can spend a weekend wiring YAML instead of shipping the feature

A VPS is the fastest path to a real deployment. Done right, it is also surprisingly safe.

My default deploy pattern is simple:

  • Run the app on 127.0.0.1 (Node/Next/Vite/Express)
  • Put Nginx in front of it
  • Only expose 80 and 443 to the internet

The Goal

Not "perfect security." Just:

  • No password logins
  • No root access over SSH
  • A firewall that blocks everything you didn't explicitly ask for
  • Automatic security updates
  • Enough visibility to know when something is wrong

I'm going to assume Ubuntu or Debian because that's what most of us ship on when we want to move fast.

Before you start, open two terminals:

  1. One connected to the server (your current session)
  2. One ready to test logging in as your new user

That way you do not lock yourself out mid-hardening.

1) Create a non-root user and lock down SSH

Create a user and give it sudo:

adduser dev
usermod -aG sudo dev

Copy your SSH key:

mkdir -p /home/dev/.ssh
cp ~/.ssh/authorized_keys /home/dev/.ssh/authorized_keys
chown -R dev:dev /home/dev/.ssh
chmod 700 /home/dev/.ssh
chmod 600 /home/dev/.ssh/authorized_keys

Then edit SSH config:

sudo nano /etc/ssh/sshd_config

Set (or ensure) these values:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

Restart SSH:

sudo systemctl restart ssh

Now, in that second terminal, make sure you can actually log in:

ssh dev@YOUR_SERVER_IP

If you do one thing, do this one.

2) Patch the box (and reboot if needed)

sudo apt-get update
sudo apt-get upgrade -y
sudo reboot

If the kernel updated, just reboot.

3) Turn on a firewall (default deny)

If you only need SSH + HTTPS:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

If you also need HTTP (for Let's Encrypt challenges or redirects), allow 80/tcp too:

sudo ufw allow 80/tcp

The rule is simple: only open the ports you can explain.

Optional: if you have a static IP, lock SSH down to just you.

sudo ufw delete allow OpenSSH
sudo ufw allow from YOUR_IP to any port 22 proto tcp

4) Enable automatic security updates

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

This is boring, and it saves you.

5) Install fail2ban (cheap brute-force insurance)

sudo apt-get install -y fail2ban
sudo systemctl enable --now fail2ban
sudo fail2ban-client status

SSH key-only auth already blocks most of this, but fail2ban reduces noise and cuts down on log spam.

If sshd is not listed in the status output, enable it:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Make sure this exists:

[sshd]
enabled = true

Then restart:

sudo systemctl restart fail2ban

6) Don't expose what you don't mean to expose

This is where most "I got hacked" stories actually come from.

  • Don't bind admin panels to 0.0.0.0
  • Don't run databases on public ports
  • Don't expose Redis/Elasticsearch "temporarily"
  • Don't publish ports without thinking

If you need something internal, bind it to localhost:

127.0.0.1:5432

Or put it behind a private network / VPN / Tailscale.

If you are self-hosting anything with an admin UI, keep it off the public internet unless you really know what you are doing.

7) Put your app behind Nginx (keep the Node port private)

This is the pattern that keeps VPS deployments simple and safe.

Run your app on localhost:

127.0.0.1:3000

Then proxy through Nginx so only web ports are exposed:

server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

If your app is reachable on http://YOUR_SERVER_IP:3000, that is a smell. Fix it.

If you run Docker

Docker is fine. Just keep the same rule:

  • Only expose 80/443 publicly
  • Reverse proxy everything else
  • Keep secrets out of docker-compose.yml (env files or a secret manager)
  • Don't mount the Docker socket into random containers
  • Update images regularly

8) Add the minimum monitoring so you can sleep

At minimum:

  • SSH login notifications (email/Slack)
  • Disk usage alerts (servers die from full disks more often than attacks)
  • A simple uptime check for your public endpoint

You don't need enterprise monitoring. You need a smoke alarm.

The Fast Checklist

If you want the TL;DR:

  1. SSH keys only, disable root + passwords
  2. Patch and reboot
  3. UFW default deny incoming, allow only what you need
  4. Unattended security updates
  5. fail2ban
  6. Never expose internal services to the internet
  7. Nginx in front, Node on localhost
  8. Add basic monitoring and alerts

Security Doesn't End

This checklist is the baseline. It helps you not get wrecked by the bots scanning the internet 24/7.

But production is not a one-time checklist. Always gauge what risk you have on the server. If you are storing personal data, holding API keys, or connecting tools that can act on your behalf, you need to monitor and keep improving your setup.

Quick external checks (run these on your own domain)

These are not a pentest. They are just fast ways to catch common misconfigurations:

I will expand this into a deeper monitoring and scanning post later along with some custom tools I wrote that monitor my NextJS, Vite, and Supabase backed applications for common misconfigurations and leaks. For now, this is a good start for getting your app online without paying "one-click cloud" prices.


Written from home, fueled by "why is this port open" paranoia.

/

Enjoyed this post?

Get more build logs and random thoughts delivered to your inbox. No spam, just builds.